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.
ORMs and how you persist data can significantly impact your design and lead to fat domain models. Data is critical, but how you capture that data can lead you down a path where you need to realize the compromise you’re making. I will show an example of how not all data is created equal.
YouTube
Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.
What do you care about?
When you’re thinking about a domain and modeling it, what do you care about? Most often, developers focus on data and data structures. We tend to think about how we will persist data, which affects our overall design, as it should. However, domain modeling also requires us to think about the behaviors of the system that we’re going to expose.
So, we care about data and behaviors. However, as the Venn diagram illustrates, not all data applies to the behaviors we want to expose.
Here’s an example of what an Entity/Object might look like if we use an ORM.
We don’t have much going on; there are many properties with private setters and a few methods with some basic logic to set those properties.
In the real world, this might have many more properties than your mapping to your database table.
But if you look at the example, what properties are relevant to the methods that have meaningful logic?
QuantityOnHand. That’s it. We’re using it (the state) to determine if we can execute a given method such as ShipProduct, and AdjustInventory. All the other methods have some logic doing trivial input validation and don’t revolve around the state of the entity.
While this is a simple example, it illustrates that not all data in an entity is relevant for an actual domain model with business logic.
QuantityOnHand is an entirely different concern than the rest of the properties.
Focus
Not all data on an entity has the same value, needs, and utility. Looking back at the Venn diagram, you can see that certain behaviors relied on certain data/state. Not all the methods cared about all the data of the entity.
If you’re primarily thinking about persistence and data, you will end up with my first example of an entity. And as mentioned, you’ve probably come across much larger entities in the real-world. Mine’s a trivial example, but hopefully illustrates how not all data is created equally in how you want to handle it.
If you want to slim down or break apart large models, you want to focus on what the behaviors are, what business rules need to be applied against what data/state.
Focusing on this can create much slimmer and more focused models that are much easier to manage than fat domain models are.
I’m going to use Event Sourcing as an example to illustrate this. You do not need to be doing event sourcing to make your models more focused. I think it just really illustrates the concept well. If you’re unfamiliar with Event Sourcing, check out my post Event Sourcing Example & Explained in plain English
Here’s a simple event stream that has four events. We’ve recorded a series of events that occurred to a individual unique product in the warehouse.
Here’s how we can represent this with a new model in code.
All this is focused on is the quantity on hand and the behaviors that revolve around it. The name, description, price, and all the other unrelated properties can be handled in a completely different way. In those cases, that might be just a pure CRUD model and doesn’t need a domain model because it has no business logic.
Again, these are trivial examples, but you can probably think of your system or have been in one with very large models with many backing properties, and they don’t all relate.
Is the idea that you separate everything into tiny models. No, of course not. If you have some real complexity in your domain, separate it into its own model. You don’t need one model to rule them all. You also don’t need to try and force a “domain model” with useless setter methods when CRUD and trivial validation can work.
Fat domain models often occur with ORMS because we focus on how we persist data rather than the behaviors around that data if you’re using an ORM with related entities. An example is if you have an entity, that has a list of child entities (one too many). If the list of child entities was a lot, why would you want to eager load all those child entities when very few methods on your root entity even use the child entities. You want to load them in a model where the relevant methods/behaviors use them. You want cohesion.
Join CodeOpinon!
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.