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
The blue book is definitely worth a read if you want to get the best out of the DDD approach. DDD patterns are not trivial and learning the essence of each of them will help you ponder when to use which pattern, how to divide your application in layers, how to define your Aggregates, and so on.
The group of 2 entities you're mentioning isn't a Bounded Context - it's probably an Aggregate. Each Aggregate has an Aggregate Root, an Entity that serves as a single entry point to the Aggregate for all other objects. So no direct relation between an Entity and another Entity in another Aggregate that is not the Aggregate Root.
Repositories are needed to get hold of Entities that are not easily obtained by traversal of other objects. Repositories usually contain Aggregate Roots, but there can be Repositories of regular Entities as well.
In your example, Conversation seems to be the Aggregate Root. Maybe Conversations are the starting point of your application, or maybe you want to query them with detailed criteria so they are not satisfyingly accessible through simple traversal of other objects. In such a case you can create a Repository for them that will give the client code the illusion of a set of in-memory Conversations to query from, add to or delete from directly. Messages on the other hand are easily obtained by traversal of a Conversation, and you might not want to get them according to detailed criteria, just all of a Conversation's Messages at once, so they might not need a Repository.
ConversationRepository will play a role in persisting Messages, but not such a direct role as you mention. So, no AddMessage() on ConversationRepository (that method rather belongs in Conversation itself) but instead, each time the Repository will persist a Conversation, it's a good idea to persist its Messages at the same time, either transparently if you use an ORM framework such as (N)Hibernate, using ad hoc SQL if you choose so, etc.