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.
Focusing on data or “entities” can lead you to develop a system that is hard to change and, over time, littered with technical complexity. I will cover why you must think about your system’s behaviors and start being explicit.
Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.
Blinded by Data
This video/blog was inspired by this post on Reddit.
First, why is it fairly obvious that the “microservices” are a teacher, student, etc? That’s the first problem. It’s not obvious at all what “school app” does. Obviously, it’s a short post, and the author has a lot of context that’s not given, but how can anyone really answer this question without understanding what the app actually does?
It’s already been pre-defined that the “microservices” are being defined by entities. Why is that?
I’m using “microservices” in quotes because I think the definition is pretty vague from developer to developer. Rather, I’ll say that they are logical boundaries of business capabilities.
Business Capabilities are an important aspect. What does the system provide in terms of features and value? Based on the short post, we have no idea. All we were given is there’s a relationship around teachers, students, rooms, etc.
Therefore, it’s impossible to say what these boundaries should be. This is why it’s not obvious that these boundaries should be teacher, student, etc, because we have no idea what the capabilities are.
Focusing on entities will lead us to a CRUD (Create, Read, Update, Delete) based system.
What’s wrong with a CRUD-based system? Nothing if that’s your intent. But this is exactly the issue that arises if you’re trying to be entity-focused around CRUD. You’ll run into the issues from the original author when trying to slice entities up into their own “service”.
A service should be the owner of a set of business capabilities. Behind those capabilities is the actual data.
It’s easy to think you’re exposing capabilities but really still getting caught up in entities or data models. To illustrate this, I’m using the Warehouse sample application. This sample is exactly that, a sample. However, if you were to take it literally or as a “best practice” on how to generally code with DDD or Vertical Slices, you’d be heading down the wrong path. There’s nothing wrong with this sample, but taken too literally is where the issue lies.
This sample has the concept of a Sales Transaction and Procurement Transaction. Typically, a sales transaction is associated with a customer, and a procurement transaction is associated with a vendor.
Because, however, they look so similar in terms of their data properties, this sample combines the two into a single concept of a Partner.
The same is true for a transaction. I mentioned there are sales transactions and procurement transactions. Since they seem so similar, they are merged into a singular concept as a Transaction.
They aren’t the same.
So, a Customer and a Vendor might seem the same. If you were looking at data, it would appear that way. They both have a Name, Address, and a list of Transactions associated with them. So they seem the same. But they aren’t.
In the above snippet, you can see that you can SellTo() and ProcureFrom() this Partner to create the appropriate transaction type (Sale, Procurement).
However, a Sales Order and a Purchase Order are very different concepts. The Partner now needs to expose the ability to do both. When we create a Transaction, we have to Switch over the different transaction types and call the appropriate SellTo() or ProcureFrom()
This is a sample application, so take it for that. However, in the real world, a customer will likely have a credit limit when placing orders. That wouldn’t apply to a Vendor. A customer and a vendor are very distinct concepts driven by behaviors. Rather than trying to turn them into the same concept generically because the data looks the same, you’d want them to be explicit.
The same applies to a Transaction. Sure, you have a Sales Transaction and a Procurement Transaction, but in reality, other types of transactions affect the warehouse. One common one is an Inventory Adjustment from doing a stock count.
While there is overlap between these concepts, they are different and, in tern, belong to different logical boundaries. This means you have the concept of a Transaction, but it exists within each logical boundary, often using different terms. A Sales Order, Purchase Order, and Inventory Adjustment are all really different Transactions for inventory.
The issue with CRUD is a lot of the workflow is in your end-users’ heads. They understand the workflow and use generic CRUD UI’s to persist that data to entities. What they do as part of their workflows or business processes isn’t explicitly captured. Is that a bad thing? It is if you have a lot of complexity and you want to enforce business rules.
For example, if you change the price of a product in a CRUD system. Why was the price changed? Did the end-user lower the price for a few days because of a flash sale? What if we wanted to email everyone that has that product on their wish list to let them know of the sale? If we aren’t explicit about what our end users are doing, we have no idea why they are doing it. Sure, we could apply some logic, and we would know that the price is lower, but we wouldn’t know why.
In large systems, when something occurs, you often want something else to occur. A lot of this is driven by behaviors, not just the fact that data changes. In order to do this, you need to start being explicit about the actions users can take. Provide them with explicit capabilities your system provides and capture those business concepts.
This will lead you to use an Event-Driven architecture where events become first-class citizens of your system as they provide a way to let other parts of your system know when discrete business concepts have occurred.
For example, when the warehouse receives a purchase order, it publishes an event to notify other logical boundaries that something has occurred. Sales may capture that information for it’s purposes (Available to Promise).
Entities aren’t just about data. They are about behaviors. Determining how to define boundaries starts with understanding those behaviors and the business capabilities of your system. As I illustrated in the Reddit post, it doesn’t start with thinking about singular entities. If you do so and try to define boundaries, you’ll end up in a really bad place with a ton of coupling between boundaries. A big ball of mud.
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.