That’s NOT an Aggregate in Domain Driven Design

Are you frustrated that you have to open multiple files across multiple layers to make what seems like a simple change? One of the culprits for this is following structure and templates that apply patterns or concepts to solve problems you might not have. One typical case of this is using aggregate from domain drive design. In this video, I’ll give examples of where an aggregate can make sense and where it’s not and adds useless indirection.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Useless Indirection

The idea for this video/blog came from a common I received on a YouTube video on my channel where I was talking about indirection.

The sentiment of this comment is all too common. I have many similar comments and have conversations with developers all the time about this. One of the culprits to this is applying patterns or concepts that are solutions to problems you don’t have in a given context.

One such pattern is the usage of an Aggregate from Domain Driven Design. The purpose of an aggregate is to create a consistency boundary. Unfortunately, the way it’s often explained more illustrates it as an object model or hierarchy.

Aggregate Domain Driven Design

The stereotypical example is to model a shopping basket. You would have a basket that would have many basket items. Many think this is an aggregate because you cannot have a basket item without a basket. In this case, this would be the aggregate, and the Basket would be the aggregate root.

Typically you’d then use a Repository to save and fetch the aggregate out, only exposing the aggregate root (Basket) to consumers.

Aggregate Domain Driven Design

But does this need to be an aggregate?

Most commonly, aggregates are often incorrectly used to model an object/data hierarchy and to old domain logic, which I often think is a more trivial validation than complex domain logic.

However, an aggregate is about creating a consistency boundary. It’s not about modeling a hierarchy.

Do you need consistency within this aggregate?

Useless Setters

Here’s a made-up example of an aggregate based on a sample I found on GitHub.

This is a simplified example. However, you can see two methods for setting the Name and the Price of this Entity. There is also some logic for setting the price: the price must be greater than zero. To do this, it’s using a specification.

What value does the specification serve? What value do the SetName and SetPrice have? None.

The SetName method is just setting the underlying Name property. It’s useless indirection.

The SetPrice contains some validation logic, which is nice. However, the separate ProductNegativePriceSpecification is useless indirection. The SetPrice is also putting our entity in an invalid state even though it’s throwing. The caller could catch the exception and carry on.

We could just put the conditional check directly in the SetPrice method. But we can also use value objects and types to enforce a valid value directly from the caller.

Now, what value do the SetName and SetPrice have? Zero value. They are just setting the underlying properties. We’ve enforced our product price when the caller needs to construct a ProductPrice type.

We don’t have an aggregate (root). We have a data model with useless setters. Remove the SetPrice and SetName, then set the properties directly from the calling code.

Consistency Boundary

So when do you need an aggregate? Well, here’s an example of an Order Aggregate (root)

This slimmed-down version of the Order Aggregate Root illustrates what’s important. When we add an order item, we do it through the aggregate root (Order) because we want to only have a single unique product per order. Also, if we have a discount for the product, we want to use the discount with the greatest value. This is a consistency boundary. We need an aggregate and all operations to go through the root to perform this logic. We don’t want random data access code or transaction scripts managing order items. This gives us consistency.

Lastly, in the SetStockconfirmedStatus method, we’re making a state change, but we’re also publishing a domain event OrderStatusChangedToStockConfirmed. Other parts of our system likely rely on this event when that state changes. We must always publish this event when the order status changes to StockConfirmed. Again, consistency on state change and publishing an event.

Aggregate or Data Model

If you need a consistency boundary, use an aggregate and aggregate root. You’re not getting any of the benefits if you have a data model with just setters. Don’t add useless indirection. Just use a data model with transaction scripts.

Join!

Developer-level members of my YouTube channel or Patreon 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.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Should you use Domain Driven Design?

I often read comments about how Domain Driven Design is too complicated or overkill. Then there are others new to DDD that want to apply it, especially the technical patterns, everywhere. So the question is, should you use Domain Driven Design? The answer is somewhere in the middle. Let me explain.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Large System

First, let me define some context on what I’m going to refer to in the rest of this post. When talking about the design of a system, I’m talking about a large enough that affects many different roles/people/departments within an organization. Even if that organization is small, there are people that were different hats and have different roles within it.

So what I’m referring to in this post isn’t about a small application with a minimal feature set. Rather it’s a larger system that has various business capabilities for many different roles within an organization.

As a concrete example, that I’ll use for the rest of this post, let’s say we are building a system for a food delivery service. I’m naive to food delivery, however, I do have a background in transportation so the intent of using this example is everyone has likely used a food delivery service so you can probably get the high level.

Should you use Domain Driven Design?

Within this system, there are going to be various capabilities we need to provide. We have some CRM for managing all our customers, some type of ordering for customers to place orders, HR for managing the drivers who deliver the food, and the actual delivery of food from restaurants to customers.

Boundaries

One of the good things that came out of Microservices was the acknowledgment of boundaries. Each boundary defines its capabilities and the data that it owns.

Should you use Domain Driven Design?

The key thing here however is that not all boundaries are created equally. Meaning that different boundaries serve different purposes and have different levels of value within the overall system.

Some boundaries are more supporting role to the core of the business domain. In the example above, CRM and HR may be more in a supporting role for Ordering and Delivery. Supporting boundaries don’t have as much complexity based on our needs. Because of this, they may be generic enough that you can use a SaaS or 3rd party product, and possibly integrate with it.

Should you use Domain Driven Design?

Some other supporting boundaries might not be generic enough so you must develop them, but it can be more CRUD-driven without much complexity. In the example above, maybe we develop our own HR application but it’s just for keeping basic demographic data of the delivery drivers.

The Ordering and Delivery boundaries are where the complexity lies and is the heart of the system we are building. Based on requirements, maybe the Ordering boundary is using a relational database, and the Delivery boundary is using an Event Store.

Logical Boundaries

I mentioned that one good thing that came out of Microservices was boundaries. Unfortunately, there is the assumption that boundaries must be physical boundaries and must be deployed independently. However, the focus should be first on simply defining logical boundaries as I have above. You don’t have to develop microservices or independently deployable services. You can develop a monolith that is composed of logical boundaries.

Should you use Domain Driven Design?

Each boundary still owns its own data and schema. CRM and Accounting are generic and we’re leveraging external SaaS that we’re integrating with. We don’t need to build our own Accounting system or CRM, that’s not the problem or solution space.

The core of our system is likely composed of long-running business processes. When a customer orders food and we have to notify the restaurant, then have the driver pick up and deliver the food to the customer. This is a series of business processes that make up that workflow. This is where you care about Domain Driven Design. Trying to model those business processes and capture the language of our domain within our code.

In our supporting boundaries, we just don’t need to take as much analysis to model that portion of the domain. There just isn’t enough value in doing so because that’s not the solution space that we want to focus on. Those boundaries can be external or simply CRUD.

Capabilities

Since boundaries are important, how do you define what boundaries are and what capabilities do they provide?

Business processes can be specific to an individual boundary as well as span multiple boundaries. Usually, a handoff from one boundary as it completes its part starts in the next boundary.

Departments & Roles are another way of thinking about boundaries. An end-user of your system at any given time may be fulfilling a certain role for a given task. What are they trying to accomplish?

Boundaries are an authority and have the responsibility to manage or handle a certain part of the process.

Boundaries contain the knowledge of how to execute a given process. But most importantly what is its objective? What is it set out to do?

Context is King

I received this comment on a YouTube video that I thought was fitting for this post. CRUD isn’t certain types of data, like an address, name, etc. It’s about that data in a given context. If you were developing the HR module for our system, yes maybe the address for our delivery drivers is just simple CRUD. However, if we’re developing a postal/mail service, changing an address would have a lot more complexity and process with it. If you were changing your address, is it because of moving to a new location? Would something like mail forwarding be required? This would not likely be CRUD at all. In that case, there is not just “updating an address”. It’s likely more of a business concept that needs to be captured. Context is important.

Should you use Domain Driven Design?

If you’re developing a large system that has complexity at the heart, then yes, I recommend looking at some of the strategic (and tactical) patterns from Domain Driven Design. If you’re defining boundaries around capabilities and language (bounded context), guess what? You’re already doing Domain Driven Design!

If you’re writing a relatively small application without much complexity, don’t get caught up with trying to apply the tactical patterns. Notice this entire post didn’t talk about any code or patterns? That’s on purpose. Check out my post on STOP doing dogmatic Domain Driven Design

Source Code

Developer-level members of my YouTube channel or Patreon get access to the full source for any working demo application that I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Leaking Value Objects from your Domain

Value Objects are a great way to explicitly capture concepts within your domain. They are immutable, always in a valid state, provide behavior, and are defined by their value. This sounds a lot like Messages (Commands, Events) that are also immutable and should be in a valid state. However, exposing your Value Objects by using them within Commands or Events can have a negative impact on your ability to evolve your domain model.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Value Objects

First, let’s cover what Value objects are since they have many characteristics that define them. They are explicit domain concepts that should always be in a valid state. They are immutable once created, which means they are always created in a valid state. Since they cannot be mutated they also have the benefit of being defined by their value. Lastly, they should have behavior, which is a characteristic that is often overlooked.

A typical example of a Value Object is Money. Money isn’t just a decimal. Especially in a multi-currency environment. The combination of the amount and the currency is required to make it valid.

Another typical example is Distance. Again, the distance isn’t simply a number, but rather can be a number along with a unit of measure.

Messaging

Commands and Events in a Message or Event Driven Architecture look very similar to Value Objects. Messages are explicit domain concepts that are immutable and in a valid state. So can you have Value Objects inside a Command or Event?

In the example above, the PlaceOrderCommand has two Value Objects: Product and Currency. These are explicit concepts we’ve defined within a boundary.

Since messages are for the purpose of decoupling between boundaries, this means that other boundaries must be aware of these as concepts.

Putting Value Objects in your messages means you’re going to be leaking details outside of your service boundary. The consequences of this are that since messages are contracts, you’ll need to think about versioning any time you want to change a Value Object since it’s a part of a Message.

Rather, you want to keep domain concepts from leaking outside of your service boundary. Concepts within your service boundary you want to be able to refactor, change, rename, remove without having to concern yourself with other services. The purpose of messaging is decoupling and using messages as a stable contract. The moment you leak something like a Value Object into a message, you’ve coupled other services to concepts within your service boundary.

Conversion

Instead of leaking Value Objects, you can create Messages/DTOs that may look similar. Simply have some type of conversion that accepts your Value Objects as parameters but have the message being built be simple primitives or nested types.

As another example, that’s using a nested type.

Don’t Leak Value Objects

They are as much a domain concept as Entities are. If you’re not going to expose Entities then do not expose Value Objects. Although they may seem trivial, you’ll be handcuffed into versioning your messages if you do need to change a them in any way.

Messages are contracts for other services. You want to message contracts to have some stability. Although Value Objects have similar characteristics as messages (Commands and Events), they are meant to be internal while Messages are meant for other services boundaries.

Source Code

Developer-level members of my CodeOpinion YouTube channel get access to the full source for any working demo application that I post on my blog or YouTube. Check out the membership for more info.

Related Links

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design