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.)
You need to understand that DDD is not about primary keys, rows or tables - these are just means to implement it.
Aggregate root is usually implemented as a class, because you are expected to access all the functionality and data of the aggregate through the root. It therefore needs to have some behavior, something which primary key of the table can't have. Primary benefit of this is the encapsulation of aggregates - its logic is completely contained inside the aggregate and doesn't leak outside which greatly reduces the coupling (and as a consequence complexity) of the application.
It follows that each aggregate has its own aggregate root, so two aggregates can't have the same root (if they have, there is effectively just one aggregate).
In your case, you probably want to have two independent aggregates (ActiveEmployee, InactiveEmployee) which are backed by the same table (which is fine because it's totally out of DDD's scope). But then remember that there's actually no dbo.Employee entity in your domain model, it's just a table on a lower (persistence) layer.
Best Answer
This approach is appealing since you get the check for free and it's well aligned with the ubiquitous language. A
Car
is not driven by adriverId
, but by aDriver
.This approach is in fact used by Vaughn Vernon in it's Identity & Access sample bounded context where he passes a
User
aggregate to aGroup
aggregate, but theGroup
only holds onto a value typeGroupMember
. As you can see this also allows him to check for the user's enablement (we are well aware that the check may be stale).However, by passing the
Driver
instance you also open yourself to an accidental modification of theDriver
withinCar
. Passing value reference makes it easier to reason about changes from a programmer's point of view, but at the same time, DDD is all about the Ubiquitous Language, so perhaps it's worth the risk.If you can actually come up with good names to apply the Interface Segregation Principle (ISP) then you may rely on an interface that doesn't have the behavioral methods. You could perhaps also come up with a value object concept that represents an immutable driver reference and that can only be instantiated from an existing driver (e.g.
DriverDescriptor driver = driver.descriptor()
).No, you wouldn't actually. There's always an anti-corruption layer to make sure that the concepts of one context won't bleed into another. It's actually much easier if you have a BC dedicated to car-driver associations because you can model existing concepts such as
Car
andDriver
specifically for that context.Therefore, you may have a
DriverLookupService
defined in the BC responsible to manage car-driver associations. This service may call a web-service exposed by the Driver Management context which returnsDriver
instances that will most likely be value objects in this context.Note that web-services aren't necessarily the best integregation method between BCs. You could also rely on messaging where for instance a
UserCreated
message from the Driver Management context would be consumed in a remote context which would store a representation of the driver in it's own DB. TheDriverLookupService
could then use this DB and the driver's data would be kept up to date with further messages (e.g.DriverLicenceRevoked
).I can't really tell you which approach is better for your domain, but hopefully this will give you enough insights to make a decision.