100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Software Architecture

Domain-Driven Design Basics

An introduction to Domain-Driven Design concepts, bounded contexts, ubiquitous language, and aggregates, and how they inform microservice boundaries.

FoundationsIntermediate10 min readJul 10, 2026
Analogies

Domain-Driven Design Basics

Domain-Driven Design (DDD) is a modeling approach, introduced by Eric Evans, that organizes software around the business domain itself rather than around technical layers like 'controllers' and 'repositories.' Its central practical output for microservices is the bounded context: a boundary within which a specific domain model and its terminology apply consistently. The word 'Customer' might mean something different in the billing context (a party with a payment method and outstanding balance) than in the support context (a party with tickets and a satisfaction score) — DDD says both are valid, as long as each stays inside its own bounded context and doesn't try to be one universal 'Customer' model shared everywhere.

🏏

Cricket analogy: The word 'over' means something specific and consistent within the rules of cricket, but has a completely different meaning in football commentary ('game over'); DDD's bounded context says each domain's vocabulary is valid only within its own boundary.

Ubiquitous Language

Ubiquitous language means the terms used in code (class names, method names, event names) should exactly match the terms domain experts use when talking about the business, so a developer and a billing analyst both say 'Invoice becomes Overdue after the grace period,' and the code literally has an Invoice class with a method like markOverdue(). This avoids the common failure mode where the code uses generic terms like 'Record' or 'Item' that don't match how anyone in the business actually talks, forcing constant mental translation and hiding bugs where the code's behavior subtly diverges from what the business actually means.

🏏

Cricket analogy: A scorer who writes 'a batter got out via a caught-behind dismissal' rather than a vague 'player removed' matches exactly how commentators and fans talk about the game, the same precision ubiquitous language demands between code and domain experts.

Aggregates and Service Boundaries

An aggregate is a cluster of domain objects that must be kept consistent together and treated as one unit for writes, for example an Order aggregate containing its line items, where you never allow a line item's quantity to change without going through the Order's own consistency rules (like recalculating the total and checking stock). In DDD-informed microservices design, a bounded context typically maps to one or a small number of services, and each aggregate's consistency boundary maps to a single service's transaction boundary, this is precisely why order-service enforces order-total invariants internally via a normal database transaction, while cross-aggregate consistency (like inventory decrementing after an order is placed) is handled asynchronously via events rather than a distributed transaction.

🏏

Cricket analogy: A team's playing eleven must be internally consistent, you can't submit a scorecard with 12 players or two designated captains, treated as one unit that's validated together, like an aggregate's consistency rules.

java
// Order aggregate enforces its own invariants inside one transaction boundary
public class Order {
  private List<LineItem> lineItems = new ArrayList<>();
  private Money total = Money.ZERO;
  private OrderStatus status = OrderStatus.DRAFT;

  public void addLineItem(Sku sku, int qty, Money unitPrice) {
    if (status != OrderStatus.DRAFT) {
      throw new IllegalStateException("Cannot modify a confirmed order");
    }
    lineItems.add(new LineItem(sku, qty, unitPrice));
    this.total = recalculateTotal();
  }

  public void confirm() {
    if (lineItems.isEmpty()) {
      throw new IllegalStateException("Cannot confirm an empty order");
    }
    this.status = OrderStatus.CONFIRMED;
    // Cross-aggregate effect (inventory decrement) is published as an event,
    // NOT executed as part of this same transaction.
    DomainEvents.publish(new OrderConfirmed(this.id, this.lineItems));
  }
}

A practical DDD exercise called Event Storming, gathering domain experts and engineers to map out business events on sticky notes, is one of the most effective ways to discover bounded contexts and aggregate boundaries before writing any service code.

  • DDD organizes software around the business domain rather than technical layers.
  • A bounded context is a boundary within which one domain model and vocabulary apply consistently.
  • The same word (like 'Customer') can mean different things in different bounded contexts, and that's acceptable.
  • Ubiquitous language means code terms should exactly match how domain experts describe the business.
  • An aggregate is a cluster of objects kept consistent together as one transactional unit.
  • A bounded context typically maps to one or a few microservices; an aggregate's consistency maps to one service's transaction boundary.
  • Cross-aggregate consistency is usually handled asynchronously via events, not distributed transactions.
  • Event Storming is a practical technique for discovering bounded contexts and aggregates with domain experts.

Practice what you learned

Was this page helpful?

Topics covered

#SoftwareArchitecture#MicroservicesStudyNotes#SoftwareEngineering#DomainDrivenDesignBasics#Domain#Driven#Design#Ubiquitous#StudyNotes#SkillVeris