CQRS (event sourcing): Projections with multiple a

2020-02-26 10:51发布

问题:

I have a question regarding projections involving multiple aggregates on a CQRS architecture.

For example sake, suppose I have two aggregates WorkItem and Developer and that the following events happen sequentially (but not immediately)

  1. WorkItemCreated (workItemId)
  2. WorkItemTitleChanged (workItemId, title)
  3. DeveloperCreated (developerId)
  4. DeveloperNameChanged (developerId, name)
  5. WorkItemAssigned (workitemId, DeveloperId)

I wish to create a projection which is as "inner join" of developer-workitem:

| WorkItemId | DeveloperId | Title  | DeveloperName | ... |
|------------|-------------|--------|---------------|-----|
| 1          | 1           | FixBug | John Doe      | ... |

The way I am doing my projections is incrementally. Meaning I load the saved projections from the database and apply the remaining events as they come.

My problem is, the event responsible for creating a row on the projection table is WorkItemAssigned. However, that event does not carry required information from previous events (workitem title, developer name, etc.)

In order to have the required information by the time WorkItemAssigned, I have to load all events from the eventstore, keep states in-memory for all WorkItems and Developers so I have the required information by the time a WorkItemAssigned event arrives.

Sure, I could have a projection for Workitem, another for Developer and query them to retrieve their last states. But it seems like a lot of work, if I am to create projections for each aggregate separately, I might as well create a database view to inner-join them (In fact, that is what I am doing.)

I am not doing all this by hand, I am currently using a good framework called EventFlow, but it doesn´t direct me to answer this question.

This is a question on fundamentals of CQRS, and I fell I am missing something here.

回答1:

I don't think you are missing anything. Projecting read models in an event-sourced system presents a different set of problems than querying from a relational model. The problems are not necessarily easier or harder to solve; they are just different.

The good news is that you have a lot of choices. Event Sourcing allows you to project data in any imaginable way, so you can decide on a solution that is most suitable for each individual projection. I guess the "bad" news (I would argue it's not bad news) is that the solution to the problem is not the same every time as it is with a relational system, which is to construct a query using JOINs.

You've already identified a few possible solutions:

  • Use a relational model as one of your read models
  • When a certain type of event comes in, re-query the streams that hold the data you need and use them to project on demand

You could also simply hold some data in an interim state (in memory, a document database, the file system, etc.) that allows you to look up the data and project it when needed. So keep lists of updated WorkItems and Developers where they can be read and used whenever a WorkItemAssigned event comes in.

I would say creating a relational database as an interim or permanent read model is a perfectly viable way of solving the problem, assuming you are not trying to achieve massive scalability.