Home/Learn/Microservices/Domain-Driven Design Basics

Domain-Driven Design Basics

Intermediate
Fundamentals

DDD aligns software structure with business domains; key building blocks include entities, value objects, aggregates, repositories, services, and domain events.

Overview

Domain-Driven Design (DDD), introduced by Eric Evans, is an approach to software development that places the business domain model at the centre of design decisions. The core building blocks are: Entities (objects with identity), Value Objects (immutable descriptors without identity), Aggregates (clusters of objects with a root entity), Repositories (abstractions over persistence), Domain Services (stateless business logic not owned by an entity), and Domain Events (facts that something happened). DDD is often combined with CQRS and Event Sourcing in microservices.

Entities, Value Objects & Aggregates

Entities have a unique ID and lifecycle. Value Objects are immutable and compared by value. Aggregates group related entities and value objects under an Aggregate Root that enforces invariants; all mutations go through the root.

Java — Entity, Value Object, Aggregate Root
// Entity — has an ID, mutable over time
@Entity
public class Order {
    @Id
    private OrderId id;           // strong type for ID
    private CustomerId customerId;
    private OrderStatus status;
    private List<OrderLine> lines;

    // Business method enforcing invariant
    public void addLine(OrderLine line) {
        if (status != OrderStatus.DRAFT)
            throw new IllegalStateException("Cannot modify a confirmed order");
        lines.add(line);
    }
}

// Value Object — immutable, compared by value
public record Money(BigDecimal amount, Currency currency) {
    public Money {
        Objects.requireNonNull(amount);
        Objects.requireNonNull(currency);
        if (amount.signum() < 0) throw new IllegalArgumentException("Negative money");
    }
    public Money add(Money other) {
        if (!currency.equals(other.currency)) throw new IllegalArgumentException("Currency mismatch");
        return new Money(amount.add(other.amount), currency);
    }
}

// OrderLine is part of the Order aggregate — accessed only via Order root
public record OrderLine(ProductId productId, int quantity, Money unitPrice) {
    public Money lineTotal() { return unitPrice.multiply(quantity); }
}

Repositories & Domain Services

Repositories provide collection-like access to aggregate roots. They hide persistence details from the domain. Domain Services implement stateless business logic that doesn't naturally belong to a single entity.

Java — Repository interface + Domain Service
// Repository interface in the domain layer
public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    void save(Order order);
    List<Order> findByCustomer(CustomerId customerId);
}

// Infrastructure-layer implementation (Spring Data JPA)
@Repository
public class JpaOrderRepository implements OrderRepository {
    private final SpringOrderRepository springRepo;
    // ...map JPA entity ↔ domain model
}

// Domain Service — spans multiple aggregates
@Service
public class PricingService {
    private final PromotionRepository promotionRepo;

    // Logic belongs here, not inside Order or Promotion alone
    public Money calculateTotal(Order order, CustomerId customerId) {
        List<Promotion> promos = promotionRepo.activeFor(customerId);
        Money subtotal = order.subtotal();
        return promos.stream()
            .reduce(subtotal, (price, promo) -> promo.apply(price), (a, b) -> b);
    }
}

Domain Events

Domain Events represent facts that occurred in the domain. They enable loose coupling between aggregates and bounded contexts. The aggregate collects events; the application layer publishes them after persisting the aggregate.

Java — Domain Events collected by Aggregate
// Domain event
public record OrderPlaced(
    OrderId orderId,
    CustomerId customerId,
    Money total,
    Instant occurredAt
) implements DomainEvent {}

// Aggregate collects events
public class Order {
    private final List<DomainEvent> domainEvents = new ArrayList<>();

    public void place() {
        if (lines.isEmpty()) throw new IllegalStateException("Empty order");
        this.status = OrderStatus.PLACED;
        domainEvents.add(new OrderPlaced(id, customerId, subtotal(), Instant.now()));
    }

    public List<DomainEvent> pullEvents() {
        var events = List.copyOf(domainEvents);
        domainEvents.clear();
        return events;
    }
}

// Application service — persist then publish
@Transactional
public void placeOrder(PlaceOrderCommand cmd) {
    Order order = orderRepository.findById(cmd.orderId()).orElseThrow();
    order.place();
    orderRepository.save(order);
    order.pullEvents().forEach(eventPublisher::publish);  // after commit
}

Key Points to Remember

  • 1Entities have identity (ID); Value Objects are immutable and compared by value.
  • 2An Aggregate Root controls all mutations inside the aggregate and enforces invariants.
  • 3Repositories provide collection-like access to aggregate roots and hide persistence.
  • 4Domain Services hold stateless logic that spans multiple entities or aggregates.
  • 5Domain Events are facts that crossed a business boundary — publish them after persisting.
  • 6Bounded Context = explicit model boundary with its own Ubiquitous Language.

Interview Questions

Sign in to ask Aria
1

What is the difference between an Entity and a Value Object?

EasyThoughtWorks
2

What is an Aggregate Root and why should external code only access it through the root?

MediumPivotal
3

What is a Domain Event and how does it differ from an application event?

MediumAmazon
4

How does DDD's Repository differ from a Spring Data JpaRepository?

MediumNetflix
5

Explain the difference between a Domain Service and an Application Service in DDD.

HardUber

Ask Aria about Domain-Driven Design Basics

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…