After dealing with DDD for months now, I'm still confused about the general purposes of domain services, factories and aggregate roots in relation to each other, e.g. where they overlap in their responsibility.
The aggregate root is responsible for ensuring that the state is consistent with the business invariant. In CQRS lingo, you change the model by issuing commands to an aggregate root.
The domain service is a query support mechanism for the aggregates. For example, a domain service might support a calculation. The command handler passes the domain service to the aggregate, the aggregate (processing a command) needs the result of the calculation, so it will pass to the domain service the parts of its state that are needed as inputs to the calculation. The domain service makes the calculation and returns the result, and the aggregate then gets to decide what to do with the result.
"Factory" can mean a couple of things. If you are using the factory to create an entity that is within the aggregate boundary, then it's just an implementation detail -- you might use it if the construction of the object is complicated.
In some circumstances, the term "Repository" is used. This usually implies that it is part of the persistence layer (not part of the domain model) responsible for creating aggregate roots. Roughly, the command handler (which is part of the application) validates a command, then uses the Repository to load the aggregate root that the command is addressed to. The command handler will then invoke the specified method on the aggregate root, passing in parameters from the command object, and possibly passing in the domain service as an argument as well.
In your examples, the key question to ask is where the responsibility for deciding whether a command should be run lives. The application is responsible for making sure that the command is well formed (all the command data is present, the data was translated into values recognized by the domain without throwing validation errors, and so on). But who gets to decide "No, you don't get to add a wheel right now -- the business rules don't allow it!"
In the DDD world, that is unquestionably the responsibility of the aggregate root. So any logic to determine that should be in the aggregate root, and the ICarService goes away.
(The alternative implementation, where the aggregate exposes its state, and the cars service checks the business rules and manipulates the state of the object, is regarded as an anti pattern -- an example of an "anemic" aggregate. "Setters" in an aggregate is a code smell. "Getters" in an aggregate are often a code smell -- especially in CQRS, where the responsibility for supporting queries is supposed to be "somewhere else" -- in the read model.)
I think you're missing the transactional analysis. Which entities will be modified by each of your use cases ? Will some of them impact more than one entity ?
What are your invariants ? Do some of them span multiple entities (e.g., two Term
s can't use the same Room
at the same date/time) ? Are they true invariants or could eventual consistency be sufficient ?
Vaughn Vernon has a good step by step approach to aggregate modelling here : https://vaughnvernon.co/?p=926
Best Answer
Typically, the composition root is about satisfying the dependencies of various modules that make up a process/application.
When Evans described DDD in the blue book, he was working in the context of a three tier architecture. You can think of the tiers as being modules - an application module, which knows about the interface(s) of the domain module but not the implementations, and a domain module that knows the interface of the persistence module, but not its implementation.
Because the coupling between modules is interface, rather than implementation, we can replace one module with another that implements the same interface, and it should all "just work".
The role of the composition root is the selection of implementations, and the wiring together of the dependency graph. So the composition root knows enough about the specific implementations to instantiate them, passing the appropriate implementation of each.
In architectures where constructor injection is favored, this might look like
Broadly, the modules describe their dependencies -- capabilities that they need but don't implement for themselves, and the composition root does the work of introducing capability consumers with capability producers.
An important thing to notice here; the pattern is deferring the binding of the consumer to a specific capability provider -- instead of forcing a choice when we build a module, we delay that choice until we build the root (or, with some additional work/assistance from DI frameworks, we can defer the choice until run time).
So for DDD, the answer is relatively simple: you use the composition root approach in those parts of your solution where you derive business value from deferring the binding of a consumer to a specific implementation.
Example: one way of persisting domain state is by writing data into an RDMBS, possibly with the help of an ORM. Another way would be to serialize that state into a document written to a document store. That might be a raw serialization of the object, or it might instead be in the form of a message/memento which is less coupled to a specific in memory representation.
Most of the app shouldn't need to care which of these choices that you make; so there can be business value in creating a seam that allows you to change this decision.
The composition root pattern gives you an element where the single responsibility is stitching these seams together into a working application.