In his book "Implementing Domain-Driven Design" Vaughn Vernon suggests to reference aggregate roots only by ID. I see the following advantages of this approach:
- It is clear where the consistency boundary, so the aggregate root, ends
- Accidential modifications of related objects can not happen
- You're not running so easily in eager/lazy loading problems, because you're forced to implement a separate way for querying related objects at once.
- You can provide just an ID instead of a complete aggregate root, what is esspecially useful when writing tests
- Testing becomes even easiert if no foreign keys are used on the database
However, espcially the last point seems to be problematic from a validation point of view. It might be easy then to pass IDs of non-existent objects, so that an invalid object would be constructed. Another problem might be, if a referenced object is removed without notice by the referencing aggregate root.
To solve the first problem, the existence of referenced objects could be checked:
class BookService(
private val authorRepository: AuthorRepository
) {
fun create(
title: String,
isbn: String,
authorId: AuthorId
): Book {
require(authorRepository.exists(authorId))
return Book(title, isbn, authorId)
}
}
But then you could also use foreign keys at the database level, because you're also forced to provide related objects (like Author
in my example). So, at least the advantage of simplified tests would no longer be existent.
How does the book notice when the author is removed from the system? That could be done with an event + listener. But then again, this is something databases solved decades ago with foreign keys.
Without references by ID only, the book would just accept an Author
instance and obviously the author would have to exist then. But if tit has to exist, one could also use the foreign keys!
From my point of view this boils down to two reasonable options:
- Not checking the existence of reference objects at all
- Using foreign keys between between aggregate roots in the database
Questions: Would you consider it acceptable to not check the references at all? Would you use foreign keys? Do see a better option?
Related
Author
instance can only exist if the author exists, then you can also ensure that anAuthorId
can only exist if the author exists, or am I missing something? It is of course up to the internals of the repository or aggregate root or whatever to maintain the consistency of the underlying data. Sometimes, the best solution is to relax consistency requirements a bit.