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
:

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:

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.