Hello!
I’m struggling to design an aggregate model that doesn’t generate some smells, and I’d love to have your insights on it.
Let’s suppose the following domain:
- Users are members of various
Workspaces, and conceptually do their work within one of them. - The main thing that a user can do in a workspace is create a
Widget, which will have various commands possible on it. - Widget names must be unique within a workspace, and creation is subject to a quota.
- For the sake of complexity, widget creation takes a dozen parameters that are subject to evolution.
As simple as it sounds, I’m faced with many tradeoffs.
First, the question is whether widget should be its own aggregate, or just an entity within the workspace aggregate. Given that users will address widgets directly, and that many widgets can be updated concurrently, I’d say it makes sense for them to be their own aggregates. I also don’t want to pollute the scope of the workspace with all the widget-specific commands.
But now, it feels like the lifecycle of the widget is split between the two aggregates:
- For creation, since we have a uniqueness constraint and quota, it seems like the creation command must be directed to the workspace aggregate, a case of aggregate creation from another aggregate. Conceptually, there are two events emitted out of this command: one part of the workspace history to check for quotas and such, and one that is the “seed event” for the new widget aggregate
- But then, further commands directly target the widget aggregate to update it
- Except… let’s imagine a command to rename a widget. Given the uniqueness constraint, this command belongs to the workspace again!
- And so on and so on…
Sooner or later, it almost makes sense to route all widget commands to the workspace, but in that case why make widget their own aggregates? But making them only entities will forbid them from being referenced from outside and make us lose concurrency…
Another problem in this design is the fact that a widget construction takes a dozen parameters. If the creation is directed to the workspace aggregate, even if it only cares about the name, it still needs to know about all the parameters in order to properly create the widget aggregate, leading to unnecessary coupling. Updating the parameters model of the widget will need updating the workspace aggregate, which shouldn’t be its responsibility.
A third option would be to challenge the initial “widgets are created through a workspace” assumption and create them directly, but then how does the workspace learn about its widgets? Doing it asynchronously removes the strict consistency of its checks.
I can’t decide on which design to settle with, would love to have your opinion on it!
Cheers,