Sorry for being late to the table on this one. This point was probably the most discussed point throughout the lifetime of the project - within the development team, with other colleagues during "show and tell" brown bag sessions, on emails/discussions
etc etc. I think the point is that, as Martin states:
"The "services" concepts in DDD are woefully confused and used interchangeably."
At the start of the (original) project, our team was fairly new to DDD which made things even harder to get to grips with. To add to this, the S#Arch docs themselves were slightly vague on the use of the Application Services layer and we ended up going round
and round in circles on this one.
Martin has explained the set up in WCHM correctly, but for clarification we have:
- Domain Objects (entities)
- External Services (contracts only) - Like repositories. These represent services that involve domain entities, but rely on external systems to produce their results
- Repository (contracts only) - Involve domain entities, but rely on internal systems to product their results
- Tasks - a fusion of the DDD Domain Services (encapsulates operations on more than one domain object) and the DDD kind of application service (entrypoint/API for clients/consumers of the domain)
How we got here is probably worth explaining...
The renaming of Application Services to Tasks was purely to remove confusion over the word "Service". Our application boundary is not implemented as any kind of service (although it easily could be), but we wanted to get away from that perception
and remove one of the Service definitions in the code. Whilst this is not strictly a DDD concept, the use of language in DDD is highly important and we found that this simple name change eliminated a lot of the discussions immediately - it seemed to make sense
straight away. For the record, this idea originally came from a course I attended by JP Boodhoo, who always called his application boundary layer "Tasks".
The lack of a Domain Services layer is a bit more haphazard than a deliberate conscious decision. In our original application at the beginning of the project, due my misunderstanding, I tried to mandate the use of the DomainServices, but this resulted
in a bunch of classes that didn't really do anything other than just hand off to the repositories. We only really had a couple of places where the use of this extra layer could be justified, e.g. a Catalog class, that provided business functionality around
Products and Categories (domain entities, different aggregate roots). Which gave us:
Controller -> Tasks -> DomainService -> Repository
New people joining the team questioned the use and value of the DomainService layer and the natural response seemed to be, why doesn't this go in the Tasks layer. My original intention had to been to set out to "do DDD properly", i.e. follow the
definitions by the book, however, in practice we ended up taking a lot more pragmatic approach of dropping this and began to add business functionality into the Tasks. It just seemed to be easier for people to understand.
So, as Martin assumes, we did have some classes that would fit into this definition in our original application. However, over time, we migrated towards adding this logic into the Tasks, giving us:
Controller -> Tasks -> Repository
WCHM is a much simpler application, and our thinking around the Tasks had evolved by this point into seeing it as a fusion of "DDD Domain Services" and "Application Services". So, no DomainServices in WCHM. If I was starting our
from scratch again, I'd probably stick with this approach - it seemed to work best for us.
The other thing I went back and forward on a bit was around external services. Ultimately these are just other repositories of data, regardless of whether we own it, or if it comes from somewhere else. So, our original application has some external service
implementations called IRepositories. However, this wasn't ever mandated and we ended up with a few IService, and some IRepository. In WCHM we're consistent - external data sources are IService and any sources of our own data (e.g. SQL and FNH) are IRepository,
but part of me would like to change them all to be IRepositories and let the implementations handle *how* the data is accessed. That's probably another discussion...
Anyway, I hope all this helps and clears some of it up. Happy to be involved in further discussions around this...