Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "join" two Aggregate Roots when preparing View Model?

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:

  1. Enrich your domain event with all data you need in event handlers. But as i said i cannot do it for Aggregates Roots.
  2. Use available data from View Model. I.e. load 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.

like image 803
Dmitry Schetnikovich Avatar asked Feb 18 '11 18:02

Dmitry Schetnikovich


2 Answers

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.

like image 193
Yves Reynhout Avatar answered Nov 17 '22 17:11

Yves Reynhout


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.

like image 3
Chris Nicola Avatar answered Nov 17 '22 16:11

Chris Nicola