Aggregates, Services and Entities
My post about entities and services seem to have hit a nerve, because it is probably my most popular post ever. I got some really good feedback on it. This post is mostly to ask questions, not neccesarily give answers. To make the discussion a bit more focused, here are the scenarios that I am talking about, in the context of a web-based application:
- Display a list of customer's orders.
- Calculate total order cost
- Display all the items in an order
- Create new order
- Add new item to order
- Print full reciept for the order
Quite a few people didn't like the fact that the service is tightly coupled to the implementation of Order.CalculateCost(). Stephen suggested that I would use this type of code:
If that is the case, there isn't any difference between the service and a repository. The problem that I am trying to solve is actually performance, I want to load all the OrderLines assoicated with the order so I could make calculations over them. The code above would either cause lazy load, which is inefficent, or would always load everything, which is also inefficent.
Udi suggested building a set of interfaces for the actions that the class can take, and assoicate fetching strategies with each action, this way we decouple the service from the details of the CalculateCost() implementation. I like the idea, but I am not sure if this isn't too much overhead for the scenario. Decoupling the fetching strategy is good, but I think that it is more appropriate for more complex problems.
Paul suggest going even further with the DDD route. I am pretty sure that I don't understand what he means, so I would like it if someone could educate me. He had this code sample:
public T FindOne(IFetchSpecification fSpec)
{
fSpec.AddSpan("OrderLines");
return (T)context.Find(fSpec);
}
Which I don't think is appropriate, once again, I don't always want the order lines, I want them for specific scenarios only. And I am not sure how I can get the above code to behave in both scenarios.
(Currently) the last comment on the previous post belongs to Stephen, he suggests having an abstract type Order, and two decendants: DoesntHaveOrderLines and CalculatedOrder (which does have them). This solve the problem of knowing when to bring the data, but I really don't think that splitting the entities according to performance concerns is a good way to go.
Comments
@Ayende, I'm not proposing the split based on performance, but on the domain itself. Order is often a very vague term and usually has different contexts in the domain depending on what 'type' of order you are talking about. By splitting Order out into the 2 types as mentioned (ridiculous names intended btw) you are making them explicit in the domain model and the names you give them to start with give a good starting point to try and give them a good name (to expand the Ubiqitous Language). You may also find that by splitting Order up in this way you may identify other types of order that exist in the domain.
By doing this you start to explore and refine the domain more and it removes those damn pesky ambigious terms that keep getting thrown about.
Or to put it anther way - if you are passing Order in to a method - what srt of Order is it - one with OrderLines or without? What does the method itself expect? By making it explicit you are more clearly showing the interaction of entities in the system.
On the Service/Repository note - if you have more explicit types in your domain then yes, in this instance service pretty much becomes a repository - unless the service performs other duties. One example is if an order needs approving - the service 'could' have a method Approve(Order) - but as to whether that sort of responsibility resides on a service or on an OrderApproval class - I'm not sure. Using Aggregates (which is what the explicit Order types become) does blur the boundary between service and repository.
I would be very interested in other peoples opinions and experiences on this. I have a keen interest in DDD but I haven't come across as many good articles and discussions on real world experiences of DDD. This is probably why your original post got so much attention :)
I do not believe you have better ways. You may find more complicated ways, but for the simple issue itself, it requires a simple solution. If you give a complicated solution for everything like this, you mess up the whole project.
The key is to have a good compromise for distributed computing. The so-called ADM (anemic domain model) is indeed a good compromise (so is OR mapping)! DDD promises that a “rich” model can solve the problem, it cannot.
The real problem is DDD’s promise. You may say that if you read DDD carefully, you would know that it does not make the promise. However, it does make the promise for unsuspected readers.
I would have three Find() methods, one which used the default fetch mode (eager or lazy), and explicit Order.FindEager(id) and Order.FindLazy(id). Then if you ever change your default loading method, any cases where it truly matters would still have nice performance.
As for implementation, if you use Criteria, you could add an alias and set a fetch mode.
Comment preview