Reputation: 933
When I was reading Microservice Patterns, one of the paragraph says that Domain-Driven Design requires aggregate to follow some rules. One of the rule is "inter-aggregate references must use primary keys".
For example, it basically means that a class Book
may only have getOwnerUserId()
and shouldn't have getOwnerUser()
.
However, in Eric Evans's Domain-Driven Design, it clearly says:
Objects within the AGGREGATE can hold references to other AGGREGATE roots.
I guess it means that Book
can have getOwnerUser()
.
If my above understandings of these 2 books are correct, is the book "Microservice Patterns" wrong about aggregates? Or is there some variant of Domain-Driven Design that "Microservice Patterns" is referring to? Or, did I miss something?
Upvotes: 0
Views: 504
Reputation: 43728
"inter-aggregate references must use primary keys"
"primary key" is very RDBMS-specific so identity would be more appropriate.
"Objects within the AGGREGATE can hold references to other AGGREGATE roots."
Can, but generally shouldn't.
An Aggregate Root (AR) is a strong consistency boundary. The natural way for an AR to protect it's invariants (including from violations through concurrency) is to encapsulate it's data in a way that allows it to oversee/detect every change.
When you reference other ARs by object reference rather than identity the consistency boundary becomes blurry which makes the design much harder to reason about.
Here's a (rather silly) example:
We can see that it's not enough anymore to look at the AR's structure to know what's truly part of it's boundary and surely that could lead to issues.
Furthermore, would you know if persons will get deleted if you delete InviteList
or if changes made to persons from within InviteList
would get persisted when calling save(inviteList)
? You'd have to inspect the persistence mappings (assuming an ORM) and the cascade options to know for sure.
I'd say the primary reason to allow a direct reference to another AR would be to be pragmatic about queries that are constructed from domain objects. It's generally harder to query without such relationships (e.g. find all InviteList
that have an invitee named "Foo"
) or construct DTOs that must aggregate data from multiple ARs (e.g. InviteListDto
with all the invitee names).
However, that's also one of the many reasons CQRS have become so popular these days. If you bypass the domain model for queries entirely (e.g. plain SQL) then you do not have to make concessions in your domain for querying needs.
Here's a sample from the IDDD book by Vaugh Vernon where he talks about that very quote from Evans.
Upvotes: 0
Reputation: 21729
Both books are saying roughly the same thing using different words. I'll add mine.
An aggregate can hold a reference to other aggregates in the same bounded context. This reference is through an identifier. In many cases an identifier is a primary key (relational artifact) or a document ID (e.g. from a document database like MongoDB). Regardless, in the domain, it's just an "identifier".
It is also possible for aggregates to refer to aggregates in another bounded context. In this case the reference is not just an identifier, but a projection of the "foreign" aggregate into the current bounded context.
Think of a library system. One bounded context could be the checkout system, and another could be about books themselves. A Library Patron aggregate could have references to books within its aggregate; these references would be small objects containing just a few of the books' properties: ID, title, and author perhaps, but not the number of pages, publisher, location in the library, etc.
Upvotes: 1
Reputation: 20561
"Aggregate root" is essentially the DDD way of saying "primary key" (I suspect the reason for not saying "primary key" is that to do so would be bringing something that's more of an infrastructure concern into the domain).
If User
is a separate aggregate from Book
, Book
can only hold a User
's ID (assuming that that's the aggregate root for User
), not a User
.
Since anything outside of the User
class can only access a user by ID, however, it's probably better naming to say getUser()
vs. getUserId()
and have getUser()
return a user ID.
Upvotes: 0