Reputation: 3289
In a DDD
architecture, I have an Order
aggregate root and a Product
aggregate. When I want to add a Product
to a specific Order
, I do:
class Order
{
void AddProduct(Product p)
{
this.Products = p;
}
}
Then, OrderRepository
persists the in-memory Order.Products
colleciton to the database.
My question is: how can I get a specific Product
for a specific Order
? Given that we should not inject repositories into entities, I am not sure how to hydrate the in-memory Order.Products
collection:
class Order
{
Product GetProduct(int productID)
{
return this.Products.Where(x => x.ProductID == productID);
}
}
Or is this something that belongs to the OrderRepository
?
Upvotes: 1
Views: 783
Reputation: 13256
I know that this question has an accepted answer but I'll give my opinion anyway :)
An Aggregate Root (AR) should never reference another AR. It only ever uses either just the Id of the related AR or a Value Object (VO) that contains the Id of the related AR along with some optional extra data.
The interesting thing about the Order
-> Product
relationship is that it is a typical many-to-many relation in database terms. In the database world one would model that, typically, by creating an association (or link) table called OrderProduct
and add columns for any data related to the association, such as quantity and price. However, somehow the table we all use is called OrderItem
or OrderLine
. We do this because it makes sense to have the association closer to the order.
I have a blog post around this that still holds somewhat true: http://www.ebenroux.co.za/design/2009/09/08/many-to-many-aggregate-roots/
In the DDD world we would then not have our Order
keep a list of object references to Product
instances. Instead we would create a VO called OrderItem
that contains only the ProductId
along with the Quantity
, Price
, and a denormalized ProductDescription
since that description may change after the order has been placed and, historically, that would change what was ordered which would be a no-no.
Just some food for thought :)
Upvotes: 3
Reputation: 57249
class Order
{
void AddProduct(Product p)
{
this.Products = p;
}
}
This model is probably wrong.
(1) If the state of Product
needs to satisfy the business invariant in Order
, then Product
should be an entity within the Order aggregate but not also a separate aggregate of its own.
On the other hand, if Order
doesn't need to know anything about the state of Product
to satisfy its own invariants, then the Products collection should not contain products, but something else (probably Id<Product>
)
Hint: does it make any sense to edit a Product
by changing the Order
?
(2) Most discussions of Order
aggregates reach the conclusion that the order holds a collection of OrderItems
(note: not aggregates), where each OrderItem
holds a reference to the Product aggregate.
Shopping carts are a fairly common example case in DDD discussions
Upvotes: 3