Implementing Domain-Driven Design Patterns

1. Bounded Context Pattern

AspectDetail
DefinitionExplicit boundary inside which a domain model is consistent
ScopeSingle ubiquitous language; one team usually
Maps ToTypically one microservice (or small cluster)
Cross-ContextTranslate via Anti-Corruption Layer or Published Language

Example: Same Concept, Different Contexts

// Sales Context: Customer = lead with revenue potential
class Customer { String id; BigDecimal lifetimeValue; List<Opportunity> opps; }

// Support Context: Customer = ticket holder
class Customer { String id; String tier; List<Ticket> openTickets; }

// Billing Context: Customer = payer
class Customer { String id; PaymentMethod method; BigDecimal balance; }

2. Aggregate Pattern

RuleDescription
Root EntitySingle entry point; external code references only the root
Consistency BoundaryOne transaction modifies one aggregate
Reference by IDOther aggregates referenced by ID, not direct object
Small AggregatesPrefer small to reduce contention
InvariantsRoot enforces business invariants on its members

Example: Order Aggregate (Java)

public class Order { // Aggregate Root
    private final OrderId id;
    private final CustomerId customerId; // Reference by ID
    private final List<OrderLine> lines = new ArrayList<>();
    private OrderStatus status;

    public void addLine(ProductId productId, int qty, Money price) {
        if (status != DRAFT) throw new IllegalStateException("Order locked");
        lines.add(new OrderLine(productId, qty, price));
    }
    public Money total() { return lines.stream().map(OrderLine::subtotal).reduce(Money.ZERO, Money::add); }
}

3. Entity Pattern

PropertyDescription
IdentityHas stable unique ID over lifetime
MutableState can change; identity does not
EqualityBy ID, not by attributes
ExamplesOrder, Customer, Account

4. Value Object Pattern

PropertyDescription
No IdentityDefined entirely by its attributes
ImmutableOnce created, never modified
EqualityBy value (all fields equal)
ExamplesMoney, Address, DateRange, Coordinates

Example: Money Value Object (Java 21+ record)

public record Money(BigDecimal amount, Currency currency) {
    public Money {
        if (amount == null || currency == null) throw new IllegalArgumentException();
    }
    public Money add(Money other) {
        if (!currency.equals(other.currency)) throw new IllegalStateException("Mismatch");
        return new Money(amount.add(other.amount), currency);
    }
}

5. Repository Pattern

AspectDetail
PurposeAbstracts persistence; collection-like interface for aggregates
ReturnsAggregate roots only
ImplementationOne repo per aggregate root
Common MethodsfindById, save, delete, findBy*

Example: Repository Interface

public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    List<Order> findByCustomer(CustomerId customerId);
    void save(Order order);
    void delete(OrderId id);
}

6. Domain Event Pattern

PropertyDetail
NamingPast tense (OrderPlaced, PaymentCaptured)
ImmutabilityEvents are facts; never modified
CarriesAggregate ID, occurred-at timestamp, relevant data
PublishingVia Outbox after aggregate save

Example: Domain Event

public record OrderPlaced(
    OrderId orderId,
    CustomerId customerId,
    Money total,
    Instant occurredAt
) implements DomainEvent {}

7. Domain Service Pattern

When to UseWhy
Operation spans multiple aggregatesDoesn't naturally belong to one
External resource needede.g., currency converter, tax calculator
Stateless logicPure domain rule, no own state
Warning: Don't use domain services as a dumping ground. Most behavior should live on entities/aggregates.

8. Factory Pattern

AspectDetail
PurposeEncapsulate complex aggregate creation
Use WhenConstruction has invariants/side effects beyond constructor
FormsStatic factory method, factory class, abstract factory

Example: Order Factory

public class OrderFactory {
    public Order createFromCart(Cart cart, CustomerId customerId) {
        Order order = new Order(OrderId.generate(), customerId);
        cart.items().forEach(i -> order.addLine(i.productId(), i.qty(), i.price()));
        order.applyDiscounts(discountPolicy.eligible(customerId));
        return order;
    }
}

9. Specification Pattern

AspectDetail
PurposeEncapsulate business rule as composable predicate
Operationsand, or, not for combining
UsesValidation, query criteria, selection rules

Example: Specification

public interface Specification<T> { boolean isSatisfiedBy(T t); }

Specification<Order> eligibleForFreeShipping = o ->
    o.total().amount().compareTo(new BigDecimal("50")) >= 0
    && o.shippingAddress().country().equals("US");

10. Ubiquitous Language Pattern

PrincipleDetail
Shared VocabularySame terms used by domain experts, devs, code, tests
Per Bounded ContextEach context has its own dictionary
Refactor TogetherIf language changes, code/docs change too
Tests as SpecTest names mirror business statements

11. Context Mapping Pattern

RelationshipDescription
PartnershipTwo contexts coordinate releases
Shared KernelSmall shared model both depend on (use sparingly)
Customer/SupplierUpstream serves downstream priorities
ConformistDownstream conforms to upstream model as-is
Anti-Corruption LayerDownstream translates from upstream
Open Host ServiceUpstream exposes published protocol for many consumers
Published LanguageWell-documented shared format (e.g., schema)
Separate WaysNo integration; contexts independent
Big Ball of MudRecognize chaotic legacy; isolate via ACL