Closed afhswe closed 3 years ago
It's about Separation of Concerns. Check out module "Working with Many-to-one Relationships", clip "Many-to-one Relationships: IDs vs. Navigation Properties". In short: Ids are database concerns that leak to the domain model.
Regarding your second question, check out this comment thread: https://enterprisecraftsmanship.com/posts/link-to-an-aggregate-reference-or-id/#comment-3943549567
Thanks for the quick response! Interesting I already know this discussion from your blog you are referring to :-) Still I have to say I prefer the approach also illustrated by Vaughn Vernon in the red book - referencing another aggregate by identity. I hope you are not already tired of this discussion but as I agree with so many other things with you concerning DDD I am just eager to learn if there is something I'm completely missing out.
I do agree with you that surrogate IDs (like in your course example which is created by the database) which have no meaning to the business should be avoided to be known to the domain model. But in many cases identifiers are natural and known to the business people.
In your concrete case I would have rather used some natural identifier for the Course, e.g. the course name or a unique course identifier which is also used by business people. I remember from studies at the university (almost 2 decades ago ;-)) that there was some alphanumeric string for courses which was known to everyone, students, professors and administrative university employees.
So I would rather prefer to use something like this as identifier. This of course means there is no surrogate id generated by the infrastructure layer but rather something in the domain would dictate at least an interface for id generation (e.g. as method on a repository interface).
The reason why I am used to do it this way is also because I'm usually working with companies where distributed architectures (such as microservices architectures) are dominant and bounded contexts do not reside in the same application. And if I reference an aggregate from another bounded context which also resides in a different application and with that in a different data store I prefer using an external identifier - usually encapsulated by a value object as suggested by Vaughn Vernon.
In this case I have an external aggregate reference that has a meaning in the business context and in my current aggregate (here Student) I would not even have to care about where this aggregate really leaves or if the identifier is a natural id or just a surrogate id.
So I am curious how you would handle the Student to Course relationship if they would not reside in the same application. Would you still let this stuff be handled by entity framework or some place else and still use navigation properties and make comparisons to see if the same course has already been enrolled based on object reference rather than using the identifier - such as a course number? And let's make it little more complicated by saying the Course has a lot more properties which the Student aggregate really does not even care about.
Wouldn't a value object with just the course number and course information required be a better fit than to create the relationship from Student to Course?
Regarding natural IDs -- they are better than surrogate Ids in a sense that they aren't DB concerns leaking into domain model, but they too have a major issue: natural Ids don't represent the entity's inherent identity. I tried to express this POW in this article: https://enterprisecraftsmanship.com/posts/entity-identity-vs-database-primary-key/ This is similar to trying to identify people by their names. Most people's names are different but sometimes collisions take place (either naturally or because of an input mistake). Still, we don't treat 2 people as the same person, even if their names match. Same for courses -- the only way to reliably assign a unique identity to the course is to do that automatically, with no input from the user or external systems. Ids must be intangible. In other words, they must be surrogate. Martin Fowler had a section in his enterprise patterns book where he came to the same conclusion but for different (also valid) reasons.
This is an ideal scenario and sometimes you have to use IDs. For example, when you use documents DBs or don't use an ORM (and thus can't use lazy loading without which direct references to other aggregates are impractical). This one is also a good reason for using Ids:
And if I reference an aggregate from another bounded context which also resides in a different application and with that in a different data store I prefer using an external identifier
The ideal separation of concerns is rarely achievable and you should be pragmatic about it.
So, to answer this question:
So I am curious how you would handle the Student to Course relationship if they would not reside in the same application
I'll use IDs encapsulated into value objects too. Direct references are only for aggregates that reside in the same bounded context. Those would be surrogate Ids, though, not natural.
Thanks for your answer, I think your last statement covers my concerns quite well.
So I can agree that modeling referenced aggregates as navigation property is a good pragmatic solution providing benefits as long as it is guaranteed that the referenced aggregate resides in the same bounded context.
Hi Vladimir,
I really enjoy your courses and blogs and I recently watched your course DDD and EFCore.
And there is one thing that leads to a question:
As I understand it Student and Course are aggregates on their own.
This is why I cannot come to understand why Student models Course as navigation property.
From what I have learned aggregates should reference other aggregates only by identity. It doesnt seem like Student has any business logic that requires it to know anything about the Course details rather than it's identity to hold it's business invariants.
Also you need to perform some additional stuff to make sure EFCore does not perform database operations for the course when changing the student aggregate.
What is the benefit of modeling it this way? I understand modelling child entities as navigation properties which makes total sense, but why also another aggregate (course in this case)?
If this is about the course name provided in the EnrollIn() produced error message I think this could be handled differently...
What am I missing here?