Domain-Driven Design – How to Design Aggregate Boundaries

aggregatedomain-driven-designentity

I would like to write an application something like ecommerce.

And you know that in similar applications products could have different properties and features. To simulate such an opportunity I've created the following domain model entities:

Category – this is something like "electronics > сomputers" i.e types of products. Сategories contain a list of properties (List< Property >).

Property – independent entity that contains the name, units of measure, data type. For example "name", "weight", "screen size". The same property can have different products.

Product – just contains name and list of values relating to properties. Value is an object that contains just the value field and the field id of the property.

I originally decided to make the Category like single aggregate in this scheme because for example when I add new product I need to know all data related with current category including properties related to the current category (category.AddNewProduct(product)).
But what should I do when I just need to add a new property that does not belong to any category. For example, I can not do this category.AddNewProperty(property) because it clearly says that we add the property to specific category.

Ok the next step I decided separate Property into a separate aggregate but then it will be a list with simple entities.

Of course I can create something like PropertyAggregate to keep inside list of properties and buisness rules but when I add a product, I need to have inside the category the entire list of properties belonging to this category to check the invariants. But I'm also aware that keeping the links inside the aggregate on other aggregates is a bad practice.

What are the options to design this business case?

Best Answer

In the DDD perspective, Category, Product and Property are entities: they all correspond to objects that have their own identity.

Option 1: your original design

You made Category the root of a single aggregate. On one side, this makes sense, because the aggregate shall ensure consistency when its objects are modified, and Product must have the Properties of its Category:

enter image description here

But on the other side, the single aggregate means that all its objects are related to a root that owns them, and all external references must be made via this aggregate root. This implies that:

  • one specific Product belongs to one and only one Category. If the Category is deleted, so are its Products.
  • a specific Property belongs to one and only one Category. Otherwise said, if "TV screens" and "Computer monitors" would be two categories, "TV screens:size" and "Computer monitors:size" would be two different properties.

The second point doesn't correspond to your narrative: "But what should I do when I just need to add a new Property that does not belong to any category". And it's not clear if the same Properties can be used in different Categories.

Option 2: Property outside the aggregate

If a Property exists independently of the Categories, it must be outside the aggregate. And the same if you want to share Properties between Categories (which makes sense for height, width, sizes, etc...). This seems definitively be the case.

The consequence is on the link between Property and things that belong to the aggregate: while you can navigate from the inner of the aggregate to Property, you are no longer allowed to go directly from a Property to the corresponding values. This navigability restriction can be shown in an UML diagram:

enter image description here

Note that this design doesn't prevent you to have a List<Property> in Category, with a reference semantic (e.g. java): each reference in the list refers to a sharable Property object in a repository.

The only problem with this design is that you could change a Property or delete it: as it's outside of the aggregate, the aggregate can't take care of the consistency of its invariants. But this is not a problem. It is the consequence of the DDD principles and the complexity of real world. Here a quote from Eric Evans in his seminal book "Domain-Driven Design: Tackling Complexity in the Heart of Software":

Any rule that spans AGGREGATES will not be expected to be up-to-date at all times. Through event processing, batch processing, or other update mechanisms, other dependencies can be resolved within some specified time. But the invariants applied within an AGGREGATE will be enforced with the completion of each transaction.

So yes, if you change a Property, you'll have to make sure that a service checks the Categories referring to it are updated as needed.

Option 3: Category, Property and Product in different aggregates

I just wonder if the assumption that a Product belongs to a single Category is founded:

  • I frequently see online shops proposing one Product under several Categories. For example, you'd find "Laptop Brand X Model Y" under the category "Laptops" and the category "Computers", and a "multifunction printer Z" under category "printer", "scanner" and "fax".
  • Isn't it possible that someone creates a Product first, and only later assigns it to Categories and fill the values ?
  • If you want to split a category, would you really delete its Products and then recreate them under the new categories ?

It won't simplify the aggregates, and you would have even more rules that span aggregates. But your system would be much more future proof.

Related Topic