1

Disclaimer: This question may be related to the framework I'm using to support CQRS/ES rather than the concepts themselves but many of these frameworks implement the same strategies, making me think the two are tightly coupled regardless.

CQRS tells us to...

use a different model to update information than the model you use to read information1

And in event sourcing...

The fundamental idea of Event Sourcing is that of ensuring every change to the state of an application is captured in an event object2

My design includes aggregate root objects, upon which methods are called to make changes (in my particular case called from Commands/Handlers). Those methods check the invariants and then publish an event to a bus, which in turn updates some aspect of the aggregate, typically setting properties or adding items to a collection. These events also update my read model so that I have a projection of the most recent state of the system that can be easily queried. Most of my queries simply act upon the most recent state, but occasionally I need to create a projection for an aggregate as it existed at a point in the past (hence the use of event sourcing).

As such my aggregate root and read model share a very similar "shape", so similar that I've created an interface that both implement so that I can treat them equally depending on the type of query being executed.

Given that the aggregate root and read model are so similar, and even though the aggregate appears to belong to the write model (as the commands act upon it) does it in fact belong in the read model?

Or, where does the aggregate root belong? In the write model, the read model or in a shared domain model, which seems to go against the whole CRQS idea?

2
  • 3
    "so similar that I've created an interface that both implement" - This is a very bad idea. You've taken an accidental similarity in two parts of your design and forced them to stay the same from now on, when they should have the freedom to evolve separately. May 5, 2021 at 14:17
  • 1
    @SebastianRedl is right on the money here. CQRS' different models does not mean that those models must be structurally different. In simple cases, they may be mirror images of one another; i.e. when you update the exact same fields that you read. But they each have their own individual purpose, and therefore should exist individually.
    – Flater
    Jan 31, 2022 at 10:25

1 Answer 1

1

TL;DR: Aggregate Root belongs on the command side of things, rather than the query side of things.

The AGGREGATE pattern comes from Domain Driven Design (Eric Evans, 2003); he recognized a common lifecycle management pattern in which clusters of related objects are treated as a unit for the purpose of change.

That last phrase is a big hint that we are talking about the "command" side of things.

The root object in the aggregate is just that one object with its API exposed to the host application.

The fact that our underlying model changes when handling queries vs handling commands really doesn't change that. Nor does the change from using documents/rows as our data model to using histories of events.

It is often the case that your domain model will need to "query" what was previously written down when processing a command, so there is likely to be at least some overlap between the two models.

You'll normally want the command handler to be working with the live/locked copy of the event history of the aggregate, rather than looking at cached answers, because race conditions.

But the actual functions that take histories of events and compute answers to questions? Yeah, there's a fair amount of overlap there. So you might want to share that code. Just keep in mind that reads/and writes are conceptually two different modules, and we want to be careful about coupling (can you easily change the implementation of the read model without breaking the write model, and vice versa).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.