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

ALWAYS Valid Domain Model

Always having your Domain Model in a valid state means it will be predictable. You’ll write less defensive code or conditional code because your domain objects will always be in a valid state. Using aggregates is a great way to encapsulate the state with behavior to keep the state valid. Using factories to create your aggregates is key to having a valid state from the very beginning. Here’s how you can create an always valid 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.

Aggregate

First, let’s start with defining our Aggregate as it ultimately is what will keep a valid domain model.

The example I’m going to use in this post is of a Shipment. You could think of a Shipment in the sense of Food Delivery service where you’re ordering food from the restaurant and it gets delivered to your home.

The aggregate in this scenario consists of a Shipment and two Stops. There is a Pickup Stop, which is the restaurant where the shipment starts from, and a Delivery stop which is your home where the Shipment ends. The Aggregate Root is the Shipment.

Valid Domain Model

One important aspect of this is that each stop needs to follow a progression. The initial state of a Stop is “In Transit”, then goes to “Arrived” once the delivery driver arrives at the location of the stop (either the restaurant or your home) and then finally goes to the “Departed” state when the delivery driver leaves the stop.

Valid Domain Model

Another rule is that this progression must be done for the Pickup Stop first, in its entirety before the Delivery Stop can start its progression. This makes sense because you need to arrive at the restaurant to pick up the food, leave the restaurant, arrive at the house for delivery, then leave the house.

Invariants

Based on the simplistic example above, our invariants are:

  • Shipment must have at least 2 stops
  • The First stop must be a Pickup
  • The Last Stop must be a Delivery
  • Stops must progress in order

The first three invariants must be established upon trying to create the Aggregate. The final invariant is controlled within the aggregate. In order to always be in a valid state, we must only allow the creation of a valid Aggregate that satisfies the invariants defined above. And once we have our Aggregate, we must only allow valid state transitions.

Enforcing these invariants is what will keep a valid domain model.

Factory

In order to create our Aggregate in a valid state right from the get-go, we can use a Factory. In the example below, I have a private constructor but expose two different static factory methods that force us into a good state. These factories are enforcing the first three invariants.

The Arrive() method is enforcing the last invariant that we must progress our stops in the correct order. Finally, here are the Stops (Pickup, Delivery) that enforce they transition themselves in the correct order.

Draft Mode

Often a scenario is what I call a “Draft Mode” where the invariants aren’t applicable, yet. In other words, they want to create a model that has much looser constraints.

To illustrate this with my Shipment example, you may have multiple Orders to a single Restaurant that ultimately will be placed all on the same Shipment.

In this case, the Shipment still has all the invariants but what we likely want is to have our Shipment created from a Plan. The concept of a Plan is to associate multiple orders and then generate a Shipment from them. This means we’re creating an Aggregate from another Aggregate.

ALWAYS Valid Domain Model

Having your domain model always in a valid state, right from the beginning means you’ll have to write less defensive code because you absolutely know the data is in a valid state. In my example, there will always be at least 2 stops, the first will be a Pickup, the last will be a Delivery. All stops will go through a progression, in order.

The factory is what sets everything up in a valid state and the Aggregate keeps us in a valid state.

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 Posts

Follow @CodeOpinion on Twitter

Software Architecture & Design

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