“Aggregate merging” and event sourcing
Julien on Aug 2nd 2012
The differentiating feature of event sourcing is that anything that ever happened is recorded within the system. It gives us the opportunity to do things such as event exploration/replaying, view rebuilds or business analytics. The benefits of event sourcing have been widely discussed but what about evolving such a system? Let’s explore a specific scenario: merging several aggregates together while keeping their history.
The scenario
Imagine a system where you can define filters to match certain kind of stocks (Think Microsoft or Google) and add them automatically to a given portfolio when some properties change. For instance, we would like to define a portfolio of all the US stocks that offer a positive return since the beginning of the week. Each portfolio is independent of the other, making them an ideal candidate for being promoted to a Portfolio aggregate root.

A few months later, a new feature is requested: for some particular kind of portfolio, we need to enforce that a stock is only present in one and only one portfolio of that kind. The previous model doesn’t work well anymore, the aggregate should now contain several portfolio to enforce the rules that apply between them:

How can we refactor the system while keeping the history at the same time?
Enter the world of aggregate merging
First, let’s have a look at our aggregate contracts before and after implementing the new model:
legacy systemPortfolio Aggregate
Commands: Create Portfolio Add Matching Criteria …
Events Portfolio Created Matching Criteria Added Stock Added Stock Removed … |
Shiny new systemPortfolio Group Aggregate
Commands Create Portfolio Group Create Portfolio Add Matching Criteria To Portfolio …
Events Portfolio Group Created Portfolio Created Matching Criteria Removed Stock Added To Portfolio Stock Removed From Portfolio … |
In order to migrate our history, we need a way to import the changes that happened in the past in the new system.
A naïve approach: events to commands
The first idea that may come to you (it certainly did in my case) is to take all the events from the legacy system and generate a matching commands for the new system. It would translate into the following sequence of commands:
| Legacy events
PortfolioCreated(“US stocks”) MatchingCriteriaAdded(criteria) ... |
Commands
CreatePortfolioGroup(id: 123, name:“Risk Management Group”) CreatePortfolio(groupId:123, “US Stocks”) AddMatchingCriteria(groupId:123, criteria) ... |
This is an interesting idea because one could argue that all the events are triggered by a command. So there’s a direct correlation between them. But that would be missing a few important points.
The problem with this approach is that it doesn’t take into account that part of the aggregate logic might be based on the current state of some dependency when raising events. In our case, when we add a Matching Criteria our aggregate will do something like that:
class PortfolioGroupAggregate { [...] void Handle(AddMatchingCriteria command, IStockService stockService) { Foreach(var stock in stockService.GetListedStocks()) { If(IsMatchingCriteria(stock)) } }
First, there’s the issue of DateTime.Now that will obviously return something quite different now that months ago. We could use some clever tricks such as introducing a configurable ITimeService to solve the problem, but it quickly is a bit smelly… Don’t you think?
Second, there is no guarantee that IStockService will return the same list of stocks that it did months ago when the Matching Criteria was initially added. Actually, we can even be sure that stocks have different properties now.
In the end you can make it work but it will be very error prone and you’ll have to hack around… I must admit I tried it… you don’t want to know! :-)
A better way: events to events
Another and hopefully better approach is to recognize that what happened happened in the past with a specific context that would be very difficult to reproduce today. If an event is in the legacy event store, we should have an event with the same meaning in the new one, even if the business logic evolved in the meantime. To guarantee it, we must bypass the business logic altogether and generate events from events. That way, we can almost be sure that our legacy and new systems will share a same view of the past. The sequence of events will look like that
| Legacy events
PortfolioCreated(“Us stocks”) MatchingCriteriaAdded(criteria) ... |
New events
PortfolioGroupCreated(id: 123, name:“Risk Management Group”) PortfolioCreated(groupId: 123, “US Stocks”) MatchingCriteriaAdded(groupId: 123, criteria) ... |
And the migration algorithm is quite easy to implement:
Foreach legacyEvent in legacyStore.GetAllEvents() translatedEvent = CreateNewEventMatching (legacyEvent) newStore.Append(translatedEvent)
A last note: checking that the migration worked
The problem with such a migration is that it’s difficult to assert that we didn’t lose any data when translating the events. There are probably several approach to this problem but we ended up regenerating view models in both systems and comparing that they were exactly the same. It’s not perfect, but in our case it allowed us to find several defects in the translation. By the way, SQL is great for comparing two view models, you just need to join the legacy table and the new table on all the columns you want to verify.
Filed in .NET,cqrs | No responses yet
The .Net Frog is also available in french at: