Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
How do you model a complex domain? In domain-driven design, you might think about entities, value objects, and aggregates when domain modeling. But how are you defining those? I will go over what a common modeling approach is but is backward. Let me explain why.
Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.
Ugh, Aggregates, Entities, and Value Objects
Typical advice is to figure out what your aggregates are. You define entities and value objects and then group them together to define aggregates.
Aggregates are a cluster of related objects (entities and value objects) that form a consistency boundary. Aggregates have gotten way too much attention. They’re a great pattern, but unfortunately, it is often misunderstood and often used when they likely shouldn’t be, just causing complexity.
However, as mentioned, this is usually the first route people start going down when modeling a domain is to start figuring out what their aggregates are with entities and value objects. It’s the completely wrong place to start.
Here’s an example I found that I’m re-creating for this post. Remember, I’m not suggesting this approach at all. However, I want to explain it because this is typical advice. I want to illustrate why this is a backward approach.
What was present was a dinner hosting application. What was defined at first were entities and was noun-driven.
The gist was that all the entities would exist in this dinner-hosting application. The next step was to take each entity and figure out if it would be an aggregate root.
For those unfamiliar, an aggregate root is an entity that is the gateway to your aggregate. All interactions with consumers/callers are done through the root entity.
For example, taking the Menu entity, it might look something like this if it were an aggregate root.
The Menu would have a collection of Hosts, a collection of Dinners, and likely a collection of Menu Reviews.
We would repeat this process for each entity. Here’s a smaller sample of what a few might look like.
The next step in the process is to identify what really is a root, and what is really just for reference. Because we can’t have an entity exist in the same aggregate (I disagree, but that’s a different topic). So anywhere we have an entity that is a root, we need to replace it to be a Value Object that is really just an identifier to the other aggregate.
Here’s an example where the Dinner in the Menu Aggregate, can’t really exist there. So instead we turn it into a value Object and really just reference it to the Dinner aggregate.
We then do this for all of our entities, which might now look something like this.
Here’s an example of what the entity might look like
This is the overall process, often given as advice on ways to build out aggregates in your system.
Guess what you just built? A data(base) schema. It’s not an aggregate.
I’ve said that I think the above process is a backward approach. Why? Looking at the above diagrams, what does this dinner-hosting system do? Sure, you can guess it’s a way to host a dinner, have guests associated with a dinner, and rate the dinner. But what are the actual capabilities that truly make this system? You have no idea because none of that is actually defined.
Aggregates are useful when you need a consistency boundary. That means that you need consistency because you need to perform some type of state change, and the aggregate as a whole needs to be persisted consistently. Why are you making a state change? What invariants are driving if you can or cannot perform a state change?
To answer that question, you need to know what the behaviors are that are driving state changes, which are driving the invariants.
Defining aggregates with entities and value objects without having any idea about what the behaviors are is backward. All you have a data buckets (objects). Ultimately, your behaviors are going to drive what data you have within your entities and value objects, which will drive how you define aggregates.
Behaviors determine the data you’re encapsulating.Me
If you’re driven by entities, your “behaviors” will end up being CRUD-focused.
I mentioned earlier that this approach ultimately created a data schema. And that’s reflected in our code that we’re exposing because all it is is CRUD.
I love this tweet because it hits the nail on the head. When you have normalized data structure, you’ll often need to compose (join) all the data together for any usefulness within your application.
Behaviors should guide you in your design. You don’t “Create” a dinner. A guest hosts a dinner. You don’t create a reservation. A guest makes a reservation. This might sound suttle, but it drastically affects your design when you focus on the capabilities and, ultimately, the workflows or lifecycles.
But not everything has behaviors, and that’s totally cool. CRUD is useful where appropriate. I mentioned at the very beginning that I think Aggregates are overused. An example would be a Menu. It might just be crud. Think about the actual workflows. You create a new menu, then might update an item later on when a price needs to be changed.
However, if we talk about a Dinner, is that CRUD? We really have no idea because nowhere in this post have I explained at all what the workflow would be for a dinner. If I am totally making it up right now, I’d assume there would be some complexity within a dinner because you’d need to have a host and have guests make reservations. They might possibly cancel their reservation. There’s probably a limited number of seats. What happens if you don’t fill all the seats? I’m just making stuff up, but I’m talking about behaviors and business rules, not data.
Behaviors that you expose are going to determine the data that you are encapsulating. The data consistency you need will drive your aggregates.
Without behaviors, you’re just defining a data(base) structures/schema and relationships. Don’t pretend it’s an aggregate; don’t use one when you don’t need one. You’re just adding useless complexity.
Start with behaviors when domain modeling.
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.