Skip to content

Instantly share code, notes, and snippets.

@zckkte
Created July 31, 2025 09:52
Show Gist options
  • Select an option

  • Save zckkte/bd9fc73326e14895aaeb261f669d7be3 to your computer and use it in GitHub Desktop.

Select an option

Save zckkte/bd9fc73326e14895aaeb261f669d7be3 to your computer and use it in GitHub Desktop.
marp author title paginate
true
Zack Kite
DDD in Practice
true

DDD in Practice


Agenda

  • Domain Core
  • Entities and Value Objects
  • Aggregates and Boundaries
  • Respositories
  • Application vs. Domain Services
  • Questions

Strategic and Tactical Design


Domain driven design is an approach that leverages strategic and tactical design patterns to model the business logic domain core


The Tactical Design, is a set of technical resources used in the construction of your Domain Model, these resources must be applied to work in a single Bounded Context.


Domain Core


Represents the nucleus of the domain and contains the most critical business logic and rules. The domain core is comprised of aggregates and domain services.


Domain Core in Practice

Best Practices

  1. Identify Business Capabilities: Recognize the core functionalities crucial to the domain. For example, OPS's capabilities include manage payment plans etc.; Bondlife's capabilities include generating digital documents for policies
  2. Define Boundaries: Encapsulate related business capabilities within bounded contexts. For example, in case of Bondlife; claims management is separate from document generation
  3. Aggregate Design: Implement aggregates to represent cohesive, transactional business units and inforce invariants.

What's an Aggregate?


First an anti-pattern...


Primitive Obsession

public class CurrencyConverter {

    public double convert(String fromCurrency, String toCurrency, double amount) {
        double exchangeRate = getExchangeRate(fromCurrency, toCurrency);
        return amount * exchangeRate;
    }

    private double getExchangeRate(String fromCurrency, String toCurrency) { ... }
}

Primitive obsession is an anti-pattern that refers to the excessive use of basic data types (such as integers, strings, or booleans) to represent more complex domain concepts


Value Objects


Value objects are small, immutable objects that represent a concept in your domain and have value equality based on their attributes.


Value Objects in Practice

In .NET

Using CSharpFunctionalExtensions

  • Install the CSharpFunctionalExtensions NuGet package:
Install-Package CSharpFunctionalExtensions

Example:

using CSharpFunctionalExtensions;

public class Money : ValueObject
{
    public decimal Amount { get; }
    public string Currency { get; }

    private Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public static Result<Money> Create(decimal amount, string currency)
    {
        if (amount < 0)
            return Result.Failure<Money>("Amount cannot be negative");

        if (string.IsNullOrWhiteSpace(currency))
            return Result.Failure<Money>("Currency is required");

        return Result.Success(new Money(amount, currency));
    }
}

Java Implementation

Using Lombok:

  • Add Lombok to your project dependencies.
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
    <scope>provided</scope>
</dependency>

Example using Lombok:

import lombok.Value;

@Value
public class Money {
    private final BigDecimal amount;
    private final String currency;
}

Using Java 21 (Records):

public record Money(BigDecimal amount, String currency) {
    // No need for explicit getters, equals, hashcode, and toString
}

Value Objects

Benefits

  1. Immutability
  2. Reusability
  3. Error Handling
  4. Clear Semantics
  5. Testability

Domain Entities


Entities...no, not that entity


Definition:

Domain entities represent objects with distinct identities. Identity is maintained even if the attributes change.

Example:

public class Customer : Entity
{
    public Guid Id { get; private set; }
    public string Name { get; private set; }
    ...
}

Entities in Practice

C# Example:

public class OrderId : ValueObject { ... }

public class Order : Entity<OrderId>
{
    public OrderId OrderId { get; private set; }
    public OrderNumber OrderNumber { get; private set; }
    public DateTime OrderDate { get; private set; }
}

Java Example:

import java.util.UUID;

public class Order {
    private UUID orderId;
    private int orderNumber;
    private Date orderDate;
}

So what's an aggregate again?


Aggregates


An aggregate is a design pattern that groups related entities and value objects, forming a cohesive unit within the domain model.


Aggregates:

Definition

  • Gather related entities under a single abstraction.
  • Maintain invariants over their lifetime.
  • Every aggregate has a root entity.
  • Restrict external access to maintain invariants.
public class PaymentPlan : AggregateRoot {...}

Finding Aggregate Boundaries

How to find boundaries

  1. Identify clusters of related entities.
  2. Entities within an aggregate share strong semantic relationships.
  3. Entities work together to fulfill specific business capabilities.
  4. Aggregates are often transactional boundaries.

Example: PaymentPlan


Aggregate Root

Explicitly show aggregate boundaries in the domain model.

Example:

public abstract class AggregateRoot : Entity
{
    public virtual int Version { get; protected set; }
    private List<DomainEvent> _events = new List<DomainEvent>();
}

Repositories

One-to-one correspondence between aggregates and repositories.

Example:

public abstract class Repository<T> where T : AggregateRoot
{
    // Repository methods
}

Always valid vs. Not Always Valid


"Always Valid" approach:

public class Cargo : AggregateRoot {
    public int MaxWeight { get; protected set; }

    protected IList<Product> Items { get; }

    public Cargo(int maxWeight)
    {
        MaxWeight = maxWeight;
        Items = new List<Product>();
    }

    public void AddItem(Product product)
    {
        int currentWeight = Items.Sum(x => x.Weight);
        if (currentWeight + product.Weight > MaxWeight)
        {
            throw new InvalidOperationException();
        }

        Items.Add(product);
    }
}

Or the "Not Always Valid" Approach:

public class Cargo : AggregateRoot {
    public int MaxWeight { get; protected set; }

    protected IList<Product> Items { get; }

    public Cargo(int maxWeight)
    {
        MaxWeight = maxWeight;
        Items = new List<Product>();
    }

    public void AddItem(Product product)
    {
        Items.Add(product);
    }

    public bool IsValid()
    {
        int currentWeight = Items.Sum(x => x.Weight);
        return currentWeight <= MaxWeight;
    }
}

Domain Services vs. Application Services

Domain Services

  • Reside inside the domain layer.
  • Contain domain logic
  • Do not communicate with the outside world.

Application Services

  • Outside the domain layer.
  • Communicate with the outside world.
  • Do not contain domain logic.

Questions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment