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 can take either approach and have it work well - there are, of course, pros and cons.
Entity Framework is definitely intended to suffuse your domain entities. It does work well when your domain entities and data entities are the same classes. It's much nicer if you can rely on EF to keep track of the changes for you, and just call context.SaveChanges()
when you're finished with your transactional work. It also means that your validation attributes don't have to be set twice, once on your domain models and once on your persisted entities - things like [Required]
or [StringLength(x)]
can be checked in your business logic, allowing you to catch invalid data states before you try to do a DB transaction and get an EntityValidationException
. Finally, it's quick to code - you don't need to write a mapping layer or repository, but can instead work directly with the EF context. It's already a repository and a unit of work, so extra layers of abstraction don't accomplish anything.
A downside to combining your domain and persisted entities is that you end up with a bunch of [NotMapped]
attributes scattered throughout your properties. Often, you will want domain-specific properties which are either get-only filters on persisted data, or are set later in your business logic and not persisted back into your database. Some times, you'll want to express your data in your domain models in a way that doesn't work very well with a database - for example, when using enums, Entity will map these to an int
column - but perhaps you want to map them to a human-readable string
, so you don't need to consult a lookup when examining the database. Then you end up with a string
property which is mapped, an enum
property which isn't (but gets the string and maps to the enum), and an API which exposes both! Similarly, if you want to combine complex types (tables) across contexts, you may wind up with a mapped
OtherTableId and an unmapped ComplexType
property, both of which are exposed as public on your class. This can be confusing for someone who isn't familiar with the project, and unnecessarily bloats your domain models.
The more complex my business logic/domain, the more restrictive or cumbersome I find combining my domain and persisted entities to be. For projects with short deadlines, or that don't express a complex business layer, I feel that using EF entities for both purposes is appropriate, and there's no need to abstract your domain away from your persistence. For projects which need maximum ease-of-extension, or that need to express very complicated logic, I think you're better off separating the two and dealing with the extra persistence complexity.
One trick to avoiding the trouble of manually tracking your entity changes is to store the corresponding persisted entity ID in your domain model. This can be filled automatically by your mapping layer. Then, when you need to persist a change back to EF, retrieve the relevant persistent entity before doing any mapping. Then when you map the changes, EF will detect them automatically, and you can call context.SaveChanges()
without having to track them by hand.
public class OrganisationService
{
public void PersistLicenses(IList<DomainLicenses> licenses)
{
using (var context = new EFContext())
{
foreach (DomainLicense license in licenses)
{
var persistedLicense = context.Licenses.Find(pl => pl.Id == license.PersistedId);
MappingService.Map(persistedLicense, license); //Right-left mapping
context.Update(persistedLicense);
}
context.SaveChanges();
}
}
}
Best Answer
Your fears are perfectly valid, and they are an everyday part of DDD.
You can design your domain in many ways. For one thing, always keep in mind that the aggregate is the boundary of transactional consistency. That means that a single aggregate must be saved atomically, while several aggregates may be saved in distinct transactions. Updates lost on one aggregate should not affect the other aggregates. I am sure you're well aware of these considerations, but very often people loose them from sight when modeling a demanding domain.
The second issue you are mentioning, the cost of fetching the entire aggregate, is also perfectly valid and it happens all the time. However, the question comes: Did you really prove by measuring that performance is poor? If there is evidence backing your fears, then you should take some action. Otherwise, you might consider doing nothing, too.
So, finally, to some practical advice. I don't know your domain and this might not be applicable, but did you consider promoting comment to be the aggregate, too? If one element of the aggregate is adding disproportionate cost compared to everything else, then it often makes sense to design around it and turn it into its own boss.
The second observation is purely practical. Since we are all programming for the real world, and performance is a realistic metric, I don't think that stepping aside from "pure DDD" and updating only one entity from an aggregate without truly materializing the entire aggregate is a crime. As long as that aggregate's consistency rules are observed, you won't make a mistake. This has a downside that some operations are treated special compared to other operations on the same aggregate, possibly leading to certain duplication of logic, but again - that is not a crime.
Note that on purely topological grounds, these two pieces of advice are isomorphic. Either you promote the comment to become an aggregate and then update one of them at a time, or you simply turn the blind eye and update one entity from an aggregate at a time... From point of view of the database, it will update one object inside a transaction. Therefore, these two approaches have the same end.