Resizing Event Driven Aggregates

212 views
Skip to first unread message

Brent Roose

unread,
Apr 28, 2021, 10:12:25 PM4/28/21
to DDD/CQRS
Hi all, I feel a little uncertain about posting here, though I'm really stuck on this question and would like your point on view. I'll also try to paint the picture from real-life examples, not dummy ones.

We're building a somewhat large application where we use event sourcing in some isolated parts. A few examples are the shopping cart and subscriptions. Both Cart and Subscription have well defined boundaries by the client, and thus we identified both of them as two aggregates. 

Both have an event-driven aggregate root class. Its internal state is built based on the previously stored events. Now, we noticed both aggregate roots growing in size over time. We're speaking several hundreds of lines of code. 

As far as we can tell, all those hundreds of lines of code really did belong to the individual aggregates, and it feels like there's nothing to be gained by further splitting them. Our domain experts always talk about these two concepts, "Cart" and "Subscription", as a whole; and we want our design to mimic that.

Still we were thinking about how to keep a class that's growing in size the way these aggregate roots did maintainable and testable. And so we identified a few concerns that could be refactored to separate classes. In case of the cart, there's lots of code related purely to state management; also there's some logic associated in dealing with the list of all cart items. In case of subscriptions there's quite a lot of logic in dealing with subscription periods and renewals. 

So what we came up with is a way to split our aggregate root interally into separate classes. Each such class can apply events, but from the outside you're still only ever using the one aggregate root class. In practice this means there's a `CartStateMachine`, used internally by the Cart, and it is reconsituted from the same event stream whenever a Cart aggregate root is retrieved. The Cart can use this state machine to make internal descisions.

This way our aggregate root can be split in smaller pieces, but is still a whole to the outside. We can also unit test a class like `CartStateMachine` partially in isolation, which makes our tests easier.

Now to my question: is this a legitemate approach? We were inspired by Axon's multi-entity aggregates (https://docs.axoniq.io/reference-guide/axon-framework/axon-framework-commands/modeling/multi-entity-aggregates) but it's clear that we're not working with entities in our case: the CartStateMachine doesn't have "an identity" and thus isn't an entity. The same goes for "the list of subscription periods that contains all logic to handle renewals". 

I did lots of reading on the topic, but the only answer I can ever find about how to keep aggregate roots a reasonable size is to "split them into several aggregates", which I don't see working in our case — but who knows, maybe you can show me otherwise.

Thanks,
Brent

Raoul Duke

unread,
Apr 28, 2021, 10:43:24 PM4/28/21
to ddd...@googlegroups.com
i didn't think there was any rule saying an aggregate root was only ever like literally a single .java file or some such. 

there's probably supposed to be one coherent api exposed and no more, but code wise do whatever is best i should think. 

$0.02

Rickard Oberg

unread,
Apr 28, 2021, 10:54:15 PM4/28/21
to ddd...@googlegroups.com
“All models are wrong. Some are useful.”

An aggregate is not a class. An aggregate is a conceptual model that follow certain rules. In code we can represent this in various ways but the representation is not the model.

In short, I came up against the same problem and did the same thing. A conceptual aggregate in our system (e.g user) have tons of commands that it can process and turn into events, but those commands are in separate buckets. Registration, account management, subscriptions, etc. 

Each set of commands has its own aggregate class with its own snapshot projection of past events, with whatever state it needs to make decisions about commands in the bucket it is responsible for, and yet produces events against the same aggregate id with the same transactional boundaries. 

I think of it as one person being perfectly capable of playing different roles, wearing different hats, and still be a consistent unit as a whole, not necessarily having any internal sub-entity structure.

To me it would be pretty crazy not to do this separation of concerns codewise, given enough complexity in a single conceptual aggregate.

FWIW, YMMV, and HTH
/Rickard

On 29 Apr 2021, at 10:12, Brent Roose <br...@spatie.be> wrote:


--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/82cec4c6-b02a-4250-bf9c-2e1cb5127204n%40googlegroups.com.

Greg Young

unread,
Apr 28, 2021, 10:58:42 PM4/28/21
to ddd...@googlegroups.com
There is nothing which says one aggregate is one class.

An aggregate is an aggregation of things. You might have 13 types inside of one aggregate.

Try focusing on what are the things *in* the aggregate and modelling that as a problem... It is a far smaller problem than modelling a system :)

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/82cec4c6-b02a-4250-bf9c-2e1cb5127204n%40googlegroups.com.


--
Studying for the Turing test

Ryan Marsh

unread,
Jun 9, 2021, 12:30:30 AM6/9/21
to DDD/CQRS
This answer was inadvertently helpful with a problem I’m having. Thanks. 

Kind regards, 
Ryan Marsh



Reply all
Reply to author
Forward
0 new messages