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.
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