Reputation: 19765
I apologize in advance if folks think this has been beaten to death. I've just spent the last few hours searching and reading many excellent posts here in SO but I'm still confused.
The source of my confusion is DTO vs. DDD and repositories. I want my POCO domain objects to have the smarts, and I want to get them from repositories. But it seems like I have to violate some encapsulation rules to make that work, and it seems like it can turn DTO's onto their heads.
Here's a simple example: In our catalog application, a Part could be a package that includes a number of other parts. So, it makes sense for the Part POCO to have a 'GetChildren()' method which returns IEnumerable< Part >. It might even do other stuff with the list on its way out.
But how is that list resolved? Seems like the repository is the answer:
interface IPartRepository : IRepository<Part>
{
// Part LoadByID(int id); comes from IRepository<Part>
IEnumerable<Part> GetChildren(Part part);
}
And
class Part
{
...
public IEnumerable<Part> GetChildren()
{
// Might manipulate this list on the way out!
return partRepository.GetChildren(this);
}
}
So now the consumers of my catalog, in addition to (correctly) loading parts from the repository, can also bypass some Part-encapsulated logic by calling GetChildren(part) directly. Isn't that bad?
I read that repositories should serve up POCO's but that DTO's are good for transferring data 'between layers.' A lot of part properties are computed - prices, for example, are calculated based on complex pricing rules. A price won't even be in a DTO coming from a repository - so it seems like passing pricing data back to a web service requires the DTO to consume the Part, not the other way around.
This is already getting too long. Where is my head unscrewed?
Upvotes: 10
Views: 2560
Reputation: 706
The missing part of the equation here is the behaviour of the Parts
object, and how you want to work with the aggregate. Do you need to work on individual children of each Part
to the nth recursion, or do you only ever work with a "root" Part
(ie those with no parents) and it's children as a whole?
Having a Part
aggregate root that contains a list of fairly generically typed Parts
as children seems like it wouldn't express your domain model particularly well, but you could do it and recursively lazy load each child collection. However, I'd still be very careful where infinite recursion is possible.
As to your second concern, DTOs aren't for transferring data between layers as much as they are for transferring data into and out of your application layer.
They are very useful if you're using a service oriented architecture (you mention web services, but it can be any SOA). Your services will query your repositories, do any extra work, and then map your domain objects into flat DTOs to send back to the requesting clients. DTOs should be simple, contain no logic and application function specific with the intention of being serialized.
Use your domain objects inside your application, and DTOs outside.
Upvotes: 0
Reputation: 48583
One approach that solves this problem is to move the logic into the child parts themselves - that is, change the semantics of the class so that Part
objects are responsible for transforming themselves when they are associated with a parent.
For example, if the price of a Part
is dependent on its parent Part
, the price can be determined at the following times (at a minimum):
Upon construction, if a parent Part
(and all other necessary data) is available.
In an AttachToParent(Part parentPart)
method or in response to an event, i.e., OnAttachedToParent(Part parentPart)
.
When it is needed by client code (i.e., on the first access of its Price
property).
Edit: my original answer (below) really wasn't in the spirit of DDD. It involved making domain objects simple containers, a design considered by many to be an anti-pattern (see Anemic Domain Model).
An additional layer between the Part
and IPartRepository
(I'll call it IPartService
) solves this problem: move GetChildren(part)
into IPartService
and remove it from Part
, then make client code call IPartService
to get Part
objects and their children rather than hitting the repository directly. The Part
class still has a ChildParts
(or Children
) property - it just doesn't know how to populate it itself.
Obviously, this imposes additional costs - you may end up writing or generating a lot of pass-through code for repository calls if you don't need extra business logic in most cases.
Upvotes: 2