In which a correspondent asks about Application Services, Domain Services, and the interactions between them.
I.
The word "service" is everywhere in my codebase. Can you suggest a naming convention, or directory structure, or other advice, regarding Application Services?
I hear you on the word Service popping up everywhere. Using the same word everywhere for different things makes it hard to differentiate between them.
Having that trouble myself, I have started using the term "Use Case" instead of "Application Service". Further, I do only one thing per Use Case, instead of doing several things in an Application Service.
I have a directory structure something like this in one project:
src/
Domain/
...
UseCase/
Draft/
AddDraft.php
FetchDraft.php
PublishDraft.php
SaveDraft.php
TrashDraft.php
Post/
FetchPost.php
FetchPosts.php
SavePost.php
TrashPost.php
Note that the naming is "human" and does not have "UseCase" suffixed on it. (More on that in a bit.)
II.
I'm struggling to know exactly what goes in the blog Application Service and blog non-application service. Where does user authorization ("can this user create a blog?") go? Where does validation ("is the title long enough? is the publish date in the future?") go? Are those application or non-application services?
This is where the idea of a Domain Service (cf. the DDD books by Vernon and Evans) comes into play. They say those non-application services are called Domain Services.
If a non-application service is injected into other non-application services, should all this be moved out of the Application Service? If so, the Application Service don't really then contain anything as far as I can see.
Whereas an Application Service presents a face to the world outside the Domain, the Domain Service is internal to the Domain. As such, it's totally fine to have that non-application Domain Service get injected into different Application Services (and for Domain Services to be used by other Domain Services).
And yes, that means eventually the Application Service may then contain almost nothing at all except calls to coordinate different Domain Services. I think this is part of a natural progression:
-
You used to put everything in a Controller method or Action class; then you extracted the domain logic into an Application Service.
-
Next, different Application Services needed to do the same things over and over; you extracted the shared logic to different Domain Service classes.
-
Finally, you realized that your Application Service could just use one or more Domain Service objects in a lot of cases.
Voila: all non-trivial logic is down in the Domain now. The Application Service coordinates between one or more of those Domain Services, then presents a Domain Payload back to the user interface.
And how would I name those non-application services?
I have started keeping my Domain Services grouped with their "main" entity or aggregate. For example:
Domain/
Content/
Draft/
Draft.php
DraftRepository.php
PublishDraftService.php
UseCase/
...
Note how the naming is still "human", but does have a "Service" suffix.
The problem I ran into was that, because they did not have differentiating suffixes, the "PublishDraft" Use Case had the same class name as the "PublishDraft" Domain Service. That caused local name conflicts when they were both used together. I would have had to alias one or the other in the use
statement, so I figured I might as well give one (or the other) a class name suffix, to preempt name conflicts.
In this project I went with a suffix on the Domain Service, but might just as well have gone with a "UseCase" suffix on the Use Cases instead. (You get to pick your tradeoffs here.)
III.
How do you suggest I communicate these possible failures between two different Domain Services? One option is to use the Domain Payloads, but again I think they are only for returning to the action, not between domain services. So the only other option I see really is domain-specific custom exceptions, but I've always felt a bit weird about domain specific exceptions. Do you have any thoughts on this? Any obvious option I'm missing?
I would say: definitely not via Domain Payload while within the Domain. The Domain Payload is always-and-only for reporting back across the boundary to the User Interface layer. Instead, use Exceptions within the domain, and/or some other domain-specific notification or messaging system while within the domain.
Also, to keep domain-level exceptions from escaping to the User Interface layer, the Application Services should have a catchall for exceptions emanating from the Domain. For example, add some wrapper or decorator logic on your Application Services to catch all exceptions from Domain activity, and that catchall can return a Payload (with the error in it) back to the calling User Interface code.
See the Reddit conversation about this post here.