Assume that Book
and Author
are Aggregate Roots in my model.
In read model i have table AuthorsAndBooks
which is a list of Authors and Books joined by Book.AuthorId
When BookAdded
event is fired i want to receive Author
data to create a new AuthorsAndBooks
line.
Because Book
is an Aggregate Root, information about Author
doesn't included in BookAdded
event. And i cannot include it because Author
root doesn't have getters (according to guidelines of all examples and posts about CQRS and Event Sourcing).
Usually i receive two types of answers on this question:
Author
from View Model and use it to build AuthorsAndBooks
row.The last one has some problems with concurrency. Author data can be not available in View Model at the time BookAdded
event is handling.
What approach do you use to solve this? Thank you.
As a general advice, let the event handlers be idempotent and make sure you can deal with out of order message handling (either by re-queuing or building in mechanisms to fill in missing data). On the other hand, do question why author and book are such desperate aggregate roots. Maybe you should copy from the author upon adding a book (what the f* is "adding a book", how's that a command). The problem is all these made-up examples. Descend to the real world, I doubt your problem exists.
Your question is missing some context, for example what is the user scenario that leads to this event and what is the state you are starting from? If you were writing the BDD tests for this case, what would they look like? Knowing this would help a lot in answering your question.
How you solve the problem of relating an book to an author is domain dependent. First we are assuming it makes sense for your domain to have an aggregate for Author and an aggregate for Book, for example, if I was writing a library system, I doubt I would have an aggregate for authors, since I don't care about an author without his/her book, what I care about is books.
As for the lack of getters, it's worth mentioning that aggregate roots don't have getters because of a preference for a tell-don't-ask style of OOP. However you can tell one AR to do something which then then tells something to another AR if you need. Part of what is important is the AR tells the others about itself rather than writing code where you ask it and then pass it along.
Finally, I have to ask why you don't have the author's ID at the time you are adding the book? How would you even know who the author is then? I would assume you could just do the following (my code assumes you are using a fluent interface for creation of AR, but you can substitute factories, constructors, whatever you use):
CreateNew.Book()
.ForAuthor(command.AuthorId)
.WithContent(command.Content);
Now perhaps the scenario is you are adding a book along with a brand new author. I would either handle this as two separate commands (which may make more sense for your domain), or handle the command the following way:
var author = CreateNew.Author()
.WithName(command.AuthorName);
var book = CreateNew.Book()
.ForAuthor(author.Id)
.WithContent(command.Content);
Perhaps the problem is you have no getter on the aggregate root Id, which I don't believe is necessary or common. However, assuming Id encapsulation is important to you, or your BookAdded event needs more information about the author than the Id along can provide, then you could do something like this:
var author = CreateNew.Author()
.WithName(command.AuthorName);
var book = author.AddBook(command.Content);
// Adds a new book belonging to this Author
public Book AddBook(BookContent content) {
var book = CreateNew.Book()
.ForAuthor(this.Id)
.WithContent(command.Content);
}
Here we are telling the author to add a book, at which point it creates the aggregate root for the book and passes it's Id to the book. Then we can have the event BookAddedForAuthor which will have the id of the author.
The last one has downsides though, it creates a command that must act through multiple aggregate roots. As much as possible I would try to figure out why the first example isn't working for you.
Also, I can't stress enough how the implementation you are looking for is dictated by your specific domain context.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With