Getting Started with Domain-Driven Design for Developers

There are certainly a lot of articles and books about DDD. A several years ago, when I started to learn about it, it took a little time for things to click. At that time, I was really eager to understand DDD from a developer’s perspective, but most of the material was focused on the architectural aspects. Different people learn new skills in different ways; as a developer, I needed to understand the code before grappling with theoretical concepts on paper. That doesn’t mean I lack an understanding of DDD from an architectural perspective; it’s more about the learning approach that works best for me: diving into code vs. reading theory on paper.

So, I’ve created a series of posts for developers who learn the way I do. We’ll delve a bit deeper into how some of these concepts are implemented in practice, referencing the eShopOnContainers ordering service. eShopOnContainers is an open-source microservices architecture reference implementation that employs DDD as a guiding principle. So the way you read this article is you click on the link, you study the code on GitHub and return to this page and continue reading.

What is a Domain and What Does Domain-Driven Design Mean?

I promised to minimize the theory, but you do need to at least understand what a domain is and what DDD means:

Domain

The domain refers to the business area that reflects the part of software a team of developers are working on. For example, an enterprise-level e-shop might have domains such as Ordering, Inventory, Catalog, Customer, Payment, and Shipping. Software engineers working on the Ordering domain might have limited knowledge of the logic and even the vocabulary of the Inventory domain. If you ask them something about a glitch in the Inventory part of e-shop, their likely response would be: “I don’t know, and I don’t want to know.”

Domain-Driven Design (DDD)

This leads us to the definition of DDD, where developers focus on one (or a few) domain(s) and become experts on that portion of the codebase (and business). They deeply understand the business logic of their specific domain and establish contact with business people (domain experts) who are their internal customers or stakeholders. This results in two key outcomes:

  1. Developers learn their customers’ domain-specific vocabulary and reflect it in the code. This is called Ubiquitous language , it is a fancy name for common language of the specific business. (ex. accountant set of vocabularies)
  2. Developers of the each domain team become masters of their own particular segment of the codebase.

What remains is figuring out how to make this approach work without developers from each domain stepping on each other’s toes. Essentially, that’s what the DDD methodology aims to address, enabling us to create software that is more maintainable, testable, and adaptable.

Core Concepts in Domain-Driven Design

As a developer if you start to look at at a DDD code base and try to understand what is happening there you certainly see these type of concepts if form of classes.

Entities

Entities are objects (instances of classes) that have a distinct identity running through time and states. For example, in the eShopOnContainers, ordering service, an Order entity is identifiable by its Id. In case of this code base abstract class Entity has a property of Id

public abstract class Entity
{
    int _Id;
    public virtual int Id
    {
        get
        {
            return _Id;
        }
        protected set
        {
            _Id = value;
        }
    }
// rest of Entity class 
}

All Entity type of classes, inherits from the “Entity” base class. This means they are going to inherit the Id as well as some other goodies. Look at OrderItem for example.


public class OrderItem : Entity
{
    // ... other properties and methods
}

Value Objects

Value objects (instances of classes) are descriptive aspects of the domain with no conceptual identity . These are objects that represent data that does not change over time so they are immutable. In eShopOnContainers, Address is used as a value object.

public class Address : ValueObject
{
    public string Street { get; private set; }
    // ... other properties and methods
}

Here’s an example to differentiate between an Entity and a Value Object: Consider two people named Daniel. Even if they share characteristics like a birthday or even a last name, they are completely distinct individuals. Each requires a unique identifier, such as a social security number, to distinguish them from each other. You know which Daniel you’re talking about based on this unique ID. In contrast, the address where Daniel lives is a Value Object. The address itself remains constant, even if Daniel moves to another location. The initial address is still valid and can be used by other people living at that address.

If you want to go in details please read Entities and Value Objects: Diving Deep into Domain-Driven Design.

Aggregates

Aggregates are groups of domain objects that are logically related to each other. They are used to encapsulate state and behavior and to make the domain model easier to understand and manage. In eShopOnContainers: ordering, an Order aggregate might contain several OrderItem entities and possibly some value objects (Address).

public class Order : Entity, IAggregateRoot
{
    private readonly List<OrderItem> _orderItems;
    // ... other properties and methods
}

In the example of an order aggregate, the order object is the aggregate root. The order object is responsible for managing the lifecycle of the aggregate. It also provides a way to access the other objects in the aggregate, such as the order items and the shipping address.

To learn more read Mastering DDD: A Developer’s Guide to Implementing Aggregates

Repositories

Repositories act as an abstraction layer between the domain model and the data mapping layer. They help to isolate domain code from code that deals with database operations. In the eShopOnContainers: ordering service, you might find a repository interface like IOrderRepository:

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    void Update(Order order);
    // ... other methods for querying and updating orders
}

And a concrete implementation might look like OrderRepository:

public class OrderRepository : IOrderRepository
{
    private readonly OrderingContext _context;

    public OrderRepository(OrderingContext context)
    {
        _context = context;
    }

    public Order Add(Order order)
    {
        return _context.Orders.Add(order).Entity;
    }
    // ... other methods
}

Domain Services

When an operation does not naturally fit within an entity or value object, a domain service comes into play. For example, if you need to implement complex order validation that involves multiple entities, you could encapsulate that logic within a domain service. Domain services are typically used to implement cross-cutting concerns, such as validation, security, logging, and caching.

Here is an example of a domain service:

public class OrderService
{
    public bool ValidateOrder(Order order)
    {
        // Complex validation logic here
    }
}

Domain services are typically stateless and they should not be directly exposed to the user. They should be called by other classes in the domain model, such as entities and value objects.

Domain Events

A domain event is an object that represents something that has happened in the domain. It is a way of capturing the history of the domain and making it available to other parts of the system.

Domain events are typically published by entities or aggregate roots when they change state. For example, an order aggregate might publish an event when the order is placed, when the order is shipped, or when the order is canceled.

Domain events can be consumed by other parts of the system, such as: Other aggregates, Domain services, Application logic (ex. user interface). Here is OrderStartedDomainEvent

public class OrderStartedDomainEvent : INotification
{
    public string UserId { get; }
     public Order Order { get; }
     // ...

    public OrderStartedDomainEvent(Order order, string userId, //...)
    {
        UserId = userId;
        Order = order;
        //...
    }
}

Domain Model

A domain model is a conceptual model of the business domain that is used to represent the concepts, entities, and relationships in the domain. It is the heart of domain-driven design (DDD) and it is used to guide the design and implementation of the software.

The domain model is typically implemented as a set of classes and objects. The classes represent the concepts in the domain, such as customers, products, and orders. The objects represent instances of the classes, such as a specific customer or a specific order.

Bounded Context

Do you recall the developer’s statement “I don’t know, and I don’t want to know” that I mentioned when introducing the concept of a “domain”? That sentiment captures precisely what a bounded context is, from a developer’s perspective. By partitioning the system into smaller, more manageable segments, bounded contexts simplify the development, understanding, and maintenance of the system.

A bounded context is both a logical and physical boundary that encapsulates a coherent and consistent domain or subdomain. Within this boundary, you’ll find entities, value objects, aggregates, services, and repositories that are relevant to that specific domain or subdomain.

Microservices and Bounded Contexts

Microservices architecture and DDD are highly complementary. Microservices essentially mean creating loosely-coupled, independently deployable services around specific business capabilities. This aligns perfectly with the concept of bounded contexts in DDD, which suggests that you should divide your system based on the different parts of your domain.

In eShopOnContainers: ordering, for example, the Ordering service is its own bounded context, completely isolated from the Catalog or Basket services. Each of these microservices owns its own data and logic, making them easier to develop, deploy, and scale independently.

What is next

Well this is just the start. We are going to deepen our knowledge about all of the concepts above so after reading the series of articles you should have a very good understanding of DDD and its .NET implementation in case of eShopOnContainers. Here are my incoming posts:

  1. Mastering DDD: A Developer’s Guide to Implementing Aggregates
  2. Entities and Value Objects: Diving Deep into Domain-Driven Design
  3. Repository Pattern in DDD: Bridging the Domain and Data Models
  4. “Event-Driven Development: Implementing Domain Events”
  5. “Building Domain Services: When Entities and Value Objects Are Not Enough”
  6. “Implementing CQRS: A Developer’s How-To Guide”
  7. “Unit Testing in a DDD Environment: Best Practices”


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *