CQRS Scalable Aggregates

Maxime Gélinas
2 min readSep 15, 2020

--

Aggregates are the heart of your system. They hold domain logic and are responsible for emitting events that will
eventually make your data consistent across multiples data projections and bounded contexts. Because aggregates are so
important, it is crucial to keep them small and easy to understand, but how to do this when the domain gets bigger and
bigger?

In this article, I will show you a way to reduce complexity from your aggregates and make them scale better.

Write-side domain overview

Before getting started, let review how commands are executed and turned into events.

After receiving the command sent by the user, the command handler invokes the repository to load the aggregate state in memory. Then, the command is executed on the aggregate which emits either domain events or failures. Finally, those events or failures are persisted using the repository.

e.g.

First attempt

In the first place, I will show you briefly how the write-side domain was implemented in a real-world project a worked on.
Then, we will discuss the scaling issues of this solution and how it can be improved to keep your aggregates
clean.

Here is how it looks:

As you can see, using this technique, the aggregate grows for every command we add. Because of this, it is easy to imagine how fast this newly created aggregate will turn into a hard to maintain mess as our domain grows.

Also, all command handlers will do the exact same things i.e.:

  1. Loads the aggregate in memory.
  2. Invokes the aggregate method.
  3. Saves the aggregate.

Let see how we can fix this!

Refactored solution

The trick is to let the commands and events execute/apply themselves!

By doing this, we are able to implement a new Execute() method in the aggregate base class which will execute a
command and apply the returned events.

Also, it makes it possible to generalize the Apply() method and move it up to the aggregate base class.

Now that we added the Execute() method we can invoke it right away from the command handler.

Also, you can see below, our command handler is now named IssueCommandsHandler instead of CommentIssueHandler.
Why is that? This is because we simplified enough that all command handlers will be exactly the same so we can use the
same command handle for all commands.

Our objective was to clean up our aggregate and as you can see below there is nothing left in it so I think we made it!

Note that I exposed Comments.Add() through the ISet interface for simplicity purposes. In a real-world project,
consider adding a custom collection class such as IssueComments to hold custom business rules.

The source code can be found here.

Related

- Implementing an Event Sourced Aggregate by Nick Chamberlain
- Command Handlers by Nick Chamberlain

--

--

Maxime Gélinas

Senior software developer, independent consultant and blogger