In the last post, I introduced Dependency Injection and show useful cases where it allows classes decouplng. I also wrote about the problem of how to inject a service in a class that has to be instantiated not application wide but in the business logic.
class MailThe problem is that CommentsRepository sends a mail basing on some input data (if someone has posted a comment), so we cannot instantiate Mail at the bootstrap of the script like we do with CommentsRepository or other request-lifetime objects (session-lifetime in case of a Java application instead of a Php one).
{
public function __construct(MailService $service)
{
...
}
}
class CommentsRepository
{
public function sendAMail()
{
$mail = new Mail(...); // what I should pass?
$mail->send();
}
}
The simplest solution is of course, to use a factory:
class CommentsRepositoryThis approach let CommentsRepository depending only on Mail and MailFactory (which could be an interface); dependency injection is correctly applied from the technical point of view. A Factory should be created for every object lifetime: the main services like an MVC stack are request-lifetime objects and should be create by an application factory, while other objects that are created during the execution should taken care by a smaller factory to pass where it is needed. Because a parameter in the constructor expresses dependency, CommentsRepository says that it creates Mail in its methods.
{
public function __construct(MailFactory $f)
{
$this->_mailFactory = $f;
}
public function sendAMail()
{
$mail = $this->_mailFactory->createMail();
$mail->send();
}
}
However, a factory for Mail trigger some problems:
- Every time we create a mail, also for testing purposes, we have to use a factory for obtaining an instance, that shield our code from changes in the constructor of Mail. Considering that an instance of Mail is likely to be passed around as a method parameter and we'll need to mock it every time, this is a smell that the current design has some flaws.
- In production code, if a class create a mail but does not send it, the former must use a factory and so is coupled to the mail sending classes. For instance, a collaborator of the CommentsRepository that creates a nicely formatted html Mail object.
- Serialization of a mail to send it in another moment is not possible since at the wakeup it won't know where to find its MailService.
- If Mail was a string, will you inject a StringFactory? I don't think so.
This lead to achieve a fundamental disctintion in business objects: Entities and Services. I write them starting with an uppercase letter to denote that they are precise concepts in object-oriented programming and the meaning of the two words is different from their use in common language.
- Entities are stateful; their job is to maintain their state and to be saved (and not saving itself) in some place or in memory. Services are stateless: you can instance a MailService many times, but it is always the same service for the end-user.
- Entities are newables, Services are injectable (this is Misko Hevery terminology). Entities should be create with new operator every time you need them, while Services should be created by a factory or a dependency injection container.
- Services depends on entities, while the opposite should not happen. It can happen in some programming models when they are coupled to a Service interface.
- Mail is an Entity
- String is an Entity (it is a ValueObject, but in a pure Entity/Service distinction it is an Entity)
- CommentsRepository is a Service
- MailService is, obviously, a Service
So Mail should knot know about Services, only Services could:
class MailNow, CommentsRepository is tied to MailService interface, but it was already coupled indirectly by Mail when we started refactor. Mail does not know about anything, and MailService has a dependency on a concrete but small and compact class.
{
public function __construct($title, $text)
{
...
}
}
class CommentsRepository
{
public function __construct(MailService $service)
{
...
}
public function sendAMail()
{
$mail = new Mail(...);
$this->_service->send($mail);
}
}
interface MailService
{
public function send(Mail $mail);
}
In the first refactoring, we treated Mail like it were a Service, but a similar deference should be adopted when dealing with more complex classes, while Mail is more or less a Value Object. If we wanted to create lazily the MailService, we would have inject a MailServiceFactory or a DI container in CommentsRepository. Beware of not slide towards an Anemic Domain Model, a procedural approach, putting methods that belong to Mail in Services: Mail is by the way a class, not a C structure or an array of fields.
Hope that this design satisfy you - it is very simple to test and loosely coupled as we should strive for every day.
This kind of blog posts are really what I like, and really rare.
ReplyDeleteThanks for the feedback. If you want more object-oriented posts feel free to raise questions in the comments. :)
ReplyDeleteGreat post! I am a big fan of Misko's site and am trying to learn about DI more to improve the testability and reduce the coupling of my code.
ReplyDeleteI also agree with the first commenter; these posts are very rare for the PHP world, I really love it! Keep them coming.
I'm glad you appreciate discussion on OO architecture. Other articles will come soon.
ReplyDeleteI'm aware of reacting on a relatively old blog but I just read it and found it very interesting.
ReplyDeleteI have the following question:
Why don't you make the MailService a class with a static send function and use it like this:
class CommentsRepository {
public function sendAMail() {
$mail = new Mail(...);
MailService::send($mail);
}
}
Isn't this much easier i.s.o. passing around all those parameters?
Is it only for testing purposes that you don't do this?
Probably your CommentsRepository needs also a RSSService, an TwitterService etc. etc. and you have to pass and set those all in the constructor.
So CommentsRepository need all these services? Well, I'd rather declare the dependencies in the constructor instead of hiding them and couple invariably my CommentsRepository to other static classes. If it is coupled, it cannot be unit-tested, it cannot be reused and it is more difficult to maintain (it's not only a matter of testing easiness :)
ReplyDeleteHowever, if there are twenty services, I will implement the Observer pattern and have this external services implement the Observer interface.
Ok, you convinced me :-)
ReplyDeleteAnd yes, you are very right about the observer patttern.
I subscribed to your blog, keep on blogging!
Although I am java programmer your articles are very interesting for me too.
ReplyDeleteI agree with entity / service distinction and I usually express it in my design but sometimes things are getting harder ;) For example: I have entity class, say "Product" with some data including "Code" containing not user friendly internal product identifier. "Product" entity is handy in my domain layer operations but I also want it to be easy to use by view developers. To achieve this I could add "FriendlyName" property which contains user friendly name translated using some "TranslationService". But this solution couples my entity with "TranslationService" and I need to inject it every time I create new "Product". This is a bit smelly, isn't it? I don't want web developer to directly use "TranslationService" in view layer. Maybe I should forgo sending "Product" class to view and create new DTO class with translated name. But again this smells because of code duplication. Have you any better ideas?
Php world is conquering now the object-oriented paradigm Java programmers have from 90s, so there is a greater need for architecture discussions in the php community. :)
ReplyDeleteA good rule of thumb for entities is they should be serializable. So injecting a TranslationService in product is no good, and if you need to use it in the class to maintain cohesion, you have to pass it as a method parameter.
What you need here is a Presentation Model which maps or wraps the original model classes, but it is an overkill only for one property to translate. A View Helper which you pass the original object should suffice.
Frameworks like NakedObjects solve the problem performing not only automatic dependency injection on the constructor but also on method parameters (passing the TranslationService for you). Manually this correspons to writing a ProductPresentation which composes both the Product instance and the TranslationService, with the method getName() which does some work. ProductPresentation can be a Decorator if you have an interface for Product, but it's not common to have interfaces for entities since they are rarely mocked.
Due to additional view requirements (I love new mock-ups in the middle of the sprint ;)) I have finally decided to use Presentation Model - the problem stated yesterday wasn't the only one mismatch between view and domain model. It looks nice now and web developers and unit tests are happy ;)
ReplyDeleteThank you very much for your advices.
Hi
ReplyDeleteReally nice post. The one thing what is missing for me is deeper elaboration on anemic domain model in entity/service separation.
Imagine more complex situation:
in stock application you are to create an Order. Order represents your will to buy a stock if current stock price is below some given value.
So you created a stock and it is saved in DB as "new". Next you can
change state of your Order i.e. move it to a different state like "updated", "canceled","confirmed", "executed".
I truly believe that Order should be responsible for its own state maintenance. Don't these calls look natural (o is type of Order):
o.cancel();
o.execute();
o.update(newValue)
???
Now imagine that due to business requirements the state change operation is complex i.e. to transit order from one state to another complex calculation are needed. It is still no a problem if all data needed to perform this calculations are to be found in Order class itself. But imagine the requirement (it is real requirement) that you can cancel an order only and only if current price (stored in DB) is at least 3% greater than the order value.
I see a few options here but being curious of yours.
My understanding of domain modelling is improved from when I wrote this post, and I prefer to not inject anything in entities as long as possible, if it is the case inject through a parameter, and as a last resort inject through a field.
ReplyDeleteThe example: since the entity has a dependency on the db, let's imagine this dependency is wrapped in a service $stockFinder that should be passed someway to the entity, so that the logic still resides in the entity and not in the service. Let's also suppose that there is no reference from the entity Order to the entity Stock (ManyToOne association). Then, I would do this:
o.cancel($stockFinder);
or the more complex:
o.setStockFinder(); // do this in a factory and repository for Order, we need injection here because there is wiring to accomplish
o.cancel();