Techcookies

Domain-Driven Design Architecture

Architecture, Design Patterns | Sun Apr 26 2026 | 35 min read

Domain-Driven Design & Hexagonal Architecture

Complete Beginner to Master Guide

Written for absolute beginners. Every concept is explained in plain English first, followed by a real-world analogy, ASCII diagram, and Java code example. We build one real system throughout — an Online Food Delivery App (like Swiggy or Zomato). At the end you will find 50 interview questions with detailed answers.


Table of Contents

PART A — Domain-Driven Design

  1. What is Domain-Driven Design?
  2. Core Philosophy & Why DDD Exists
  3. Ubiquitous Language
  4. Domain, Subdomain & Bounded Context
  5. The Four Layers of DDD Architecture
  6. Entities
  7. Value Objects
  8. Aggregates & Aggregate Root
  9. Domain Events
  10. Repositories
  11. Domain Services
  12. Application Services
  13. Factories
  14. Context Mapping
  15. Strategic vs Tactical DDD

PART B — Hexagonal Architecture

  1. What is Hexagonal Architecture?
  2. Ports — The Contracts
  3. Adapters — The Implementations
  4. The Dependency Rule
  5. Project Structure

PART C — DDD + Hexagonal Together

  1. How DDD and Hexagonal Relate
  2. Mapping DDD Concepts to Hexagonal Layers
  3. Complete End-to-End Example
  4. DDD + Hexagonal with Microservices
  5. Anti-Patterns to Avoid

PART D — Interview Questions

  1. 50 Interview Questions with Answers

PART A — DOMAIN-DRIVEN DESIGN


1. What is Domain-Driven Design?

Plain English First

Imagine you are hired to build software for a hospital. The hospital has doctors, patients, appointments, prescriptions, billing, and insurance. All of this real-world business territory — the problem your software must solve — is called the Domain.

Domain-Driven Design (DDD) is a way of building software where the business problem drives every design decision. The code should mirror the real world so closely that a business person — someone who has never written code — can read it and understand what it does.

It was introduced by Eric Evans in his 2003 book "Domain-Driven Design: Tackling Complexity in the Heart of Software."

The Core Idea in One Picture

WITHOUT DDD:                          WITH DDD:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Business says:                        Business says:
  "The patient needs                    "The patient needs
   a prescription"                       a prescription"

Developer writes:                     Developer writes:
  createMedDoc(                          patient
    patId,                                 .issuePrescription(
    drugCode,                                 medication,
    doseAmt,                                  prescribingDoctor
    freqCode                              );
  );

Result:                               Result:
  Only Raj understands                  ANY business person can
  what this code does.                  read this code.
  Business logic is hidden.             Business logic is obvious.
  Bugs are invisible.                   Bugs are obvious.

Real-World Analogy — The Architect and Builder

┌─────────────────────────────────────────────────────────┐
│         BUILDING A HOUSE — WITHOUT DDD                  │
│                                                         │
│  Architect draws blueprints using "architect language"  │
│  Builder receives blueprints and guesses what they mean │
│  Communication breaks down → wrong rooms, wrong doors   │
│  Cost overruns → delays → bugs                          │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│         BUILDING A HOUSE — WITH DDD                     │
│                                                         │
│  Architect and Builder meet DAILY                       │
│  They agree: "load-bearing wall" means one specific     │
│  thing. "Wet room" means one specific thing.            │
│  Everyone uses the SAME WORDS.                          │
│  Builder writes exactly what architect designed.        │
└─────────────────────────────────────────────────────────┘

2. Core Philosophy & Why DDD Exists

The Problem DDD Solves

As software grows, business logic becomes scattered, duplicated, and buried under technical noise. Teams working on different parts of the system can no longer agree on what things mean. Changing one part breaks another.

DDD solves this by giving teams:

  • A shared language (Ubiquitous Language)
  • Clear boundaries (Bounded Contexts)
  • Patterns for expressing business rules in code (Entities, Aggregates, etc.)

Three Pillars of DDD

┌────────────────────────────────────────────────────────────────────┐
│                    THREE PILLARS OF DDD                            │
│                                                                    │
│  ┌────────────────────┐ ┌─────────────────────┐ ┌──────────────┐ │
│  │  1. FOCUS ON THE   │ │ 2. COLLABORATE WITH  │ │ 3. SPEAK ONE │ │
│  │  CORE DOMAIN       │ │    DOMAIN EXPERTS    │ │    LANGUAGE  │ │
│  │                    │ │                      │ │              │ │
│  │  Your business     │ │  Developers sit with │ │  Same words  │ │
│  │  logic is your     │ │  business people     │ │  in meetings,│ │
│  │  most valuable     │ │  and model together. │ │  code, docs, │ │
│  │  asset. Protect    │ │  NOT in isolation.   │ │  and tickets.│ │
│  │  it fiercely.      │ │                      │ │              │ │
│  └────────────────────┘ └─────────────────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────────────────┘

Who are Domain Experts?

Domain Experts are people who deeply understand the business — NOT developers. They are the people whose knowledge you are encoding into software.

Our Food Delivery App — Domain Experts:

  👤 Operations Manager   → knows how orders flow from placement to delivery
  👤 Restaurant Partner   → knows how menus work, prep times, item availability
  👤 Delivery Head        → knows routing, partner assignment, distance calculation
  👤 Finance Manager      → knows payment methods, refund rules, tax calculation
  👤 Customer Support     → knows cancellation rules, complaint resolution

3. Ubiquitous Language

What is Ubiquitous Language?

Ubiquitous means found everywhere. Ubiquitous Language is a shared vocabulary agreed upon by both developers and domain experts, then used in EVERY place:

  • Spoken conversations and meetings
  • Written documents and Jira tickets
  • Code: class names, method names, variable names
  • Database table names and column names
  • API endpoint paths
  • Kafka topic names

The Problem Without It

SAME CONCEPT — DIFFERENT WORDS (Chaos):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  Operations Manager says:  "The customer PLACED an ORDER"
  Product Manager tracks:   "USER PURCHASE ticket #8823"
  Backend Developer wrote:  class FoodTransaction { }
  Database table:           tbl_food_requests
  REST endpoint:            POST /api/v1/buy-food
  Kafka topic:              food-order-events-v3

  6 different words for the same thing = confusion, bugs,
  wrong software built, expensive to maintain.

Building Ubiquitous Language — Food Delivery App

AGREED GLOSSARY — FOOD DELIVERY APP:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  Term               Meaning                            ❌ Do NOT say
  ────────────────   ──────────────────────────────     ────────────────
  Customer           Person who places food orders      User, Client
  Restaurant         Food vendor registered on app      Merchant, Seller
  Order              Confirmed request for food items   Purchase, Request
  Order Item         One dish + quantity in an order    Line item, Entry
  Cart               Temporary pre-order collection     Basket, Buffer
  Place (an order)   Submit cart → confirmed order      Create, Checkout
  Confirm            Restaurant accepts the order       Accept, Approve
  Prepare            Kitchen cooking the food           Cook, Process
  Dispatch           Food handed to delivery partner    Ship, Send
  Deliver            Food reaches the customer          Complete, Drop
  Cancel             Order stopped before dispatch      Abort, Reverse
  Delivery Partner   Person physically delivering       Driver, Rider
  Delivery Fee       Cost charged for delivery          Shipping cost
  Rating             Customer's score for the order     Review, Feedback

Applying Ubiquitous Language Everywhere

java
// ❌ BAD — technical language, meaningless to business
public class FoodTransaction {
    private String txnId;
    private String userId;
    private String statusCode;
    private double totalAmt;

    public void processTxn() { }
    public void markCompleted() { }
    public void flagForRefund() { }
}

// ✅ GOOD — business language in code
public class Order {
    private OrderId orderId;
    private CustomerId customerId;
    private OrderStatus status;
    private Money totalAmount;

    public void place() { }
    public void confirmByRestaurant() { }
    public void cancel(CancellationReason reason) { }
    public void deliver() { }
}
Apply EVERYWHERE:
  Java class:    class Order { }
  Method name:   void confirmByRestaurant()
  REST API:      POST /api/orders
  DB table:      orders
  Kafka topic:   order.placed
  Jira ticket:   "Order cancellation does not trigger refund"
  Daily standup: "The order confirmation flow is failing"

4. Domain, Subdomain & Bounded Context

Domain

The Domain is the entire subject area your software addresses — everything about your business.

┌────────────────────────────────────────────────────────────┐
│            FOOD DELIVERY APP — FULL DOMAIN                 │
│                                                            │
│  Customers, Restaurants, Menus, Orders, Payments,          │
│  Delivery, Tracking, Reviews, Promotions, Notifications,   │
│  Analytics, Customer Support, Driver Management            │
└────────────────────────────────────────────────────────────┘

Subdomains — Breaking the Domain Apart

No single team can own everything. We split the domain into Subdomains — logical groupings of related capabilities.

┌─────────────────────────────────────────────────────────────────────┐
│               FOOD DELIVERY APP — SUBDOMAIN MAP                     │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                 CORE SUBDOMAINS  ⭐                          │   │
│  │         Your competitive advantage. BUILD IN-HOUSE.          │   │
│  │                                                             │   │
│  │  📦 Order Management      — the heart of the platform       │   │
│  │  🗺️  Smart Delivery Routing — AI-based shortest path         │   │
│  │  💰 Dynamic Pricing        — surge pricing, discounts        │   │
│  │  ⭐ Restaurant Ranking     — why some show up first          │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │               SUPPORTING SUBDOMAINS                         │   │
│  │       Necessary but not your differentiator.                │   │
│  │          Build or customise off-the-shelf.                  │   │
│  │                                                             │   │
│  │  👤 Customer Profiles     🍽️  Restaurant Onboarding          │   │
│  │  📋 Menu Management       🚗 Delivery Partner Mgmt           │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                GENERIC SUBDOMAINS                           │   │
│  │   Same in every business. BUY SaaS — don't build yourself.  │   │
│  │                                                             │   │
│  │  🔐 Authentication  → Auth0 / Cognito                       │   │
│  │  📧 Email / SMS     → SendGrid / Twilio                     │   │
│  │  💳 Payments        → Razorpay / Stripe                     │   │
│  │  📊 Analytics       → Mixpanel / Amplitude                  │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

Bounded Context — The Key DDD Concept

A Bounded Context is a clear boundary inside which ONE domain model applies with ONE set of agreed meanings. The same word can mean something completely different in a different Bounded Context.

Real-world analogy: The word "interest" means:

  • In a bank's savings department: money they pay YOU
  • In a bank's loans department: money YOU pay them
  • In HR: your hobbies and passions

Same word. Three different bounded contexts. Three completely different meanings.

The Word "Order" in Different Contexts

┌───────────────────────┬───────────────────────┬───────────────────────┐
│    ORDER CONTEXT      │   PAYMENT CONTEXT     │  DELIVERY CONTEXT     │
│                       │                       │                       │
│  Order = {            │  Order = {            │  Order = {            │
│    orderId,           │    orderId,  ← ID only│    orderId,  ← ID only│
│    customerId,        │    amount,            │    pickupAddress,     │
│    restaurantId,      │    currency,          │    dropAddress,       │
│    orderItems[],      │    paymentMethod,     │    partnerId,         │
│    deliveryAddress,   │    taxAmount,         │    estimatedMinutes,  │
│    specialNote,       │    invoiceNumber      │    trackingUrl        │
│    status,            │  }                    │  }                    │
│    placedAt           │                       │                       │
│  }                    │  Cares about MONEY,   │  Cares about ROUTING, │
│                       │  not food             │  not food or money    │
│  Cares about FOOD     │                       │                       │
│  and delivery details │                       │                       │
└───────────────────────┴───────────────────────┴───────────────────────┘

INSIGHT: Each context has a focused, lean model of "Order"
         without being polluted by other contexts' concerns.
         This keeps each model simple, clear, and correct.

Bounded Context Map — Full System

                    ┌──────────────────────┐
                    │    MENU CONTEXT      │
                    │  Menu, Category,     │
                    │  MenuItem, Price      │
                    └──────────┬───────────┘
                               │ provides menu data
                               ▼
              ┌────────────────────────────────┐
              │      ORDER CONTEXT  ⭐         │
              │  Place, Confirm, Cancel,        │
              │  Dispatch, Deliver              │
              └────┬────────────────┬───────────┘
                   │                │
        OrderPlaced│event           │OrderPlaced event
                   ▼                ▼
          ┌──────────────┐  ┌───────────────────┐
          │   PAYMENT    │  │    RESTAURANT      │
          │   CONTEXT    │  │    CONTEXT         │
          │  Charge,     │  │  Accept, Prepare,  │
          │  Refund,     │  │  Mark Ready        │
          │  Invoice     │  │                    │
          └──────┬───────┘  └─────────┬──────────┘
                 │                    │
                 │ PaymentSuccess      │ FoodReady event
                 │ event              │
                 └──────────┬─────────┘
                            ▼
                 ┌────────────────────┐
                 │  DELIVERY CONTEXT  │
                 │  Assign Partner,   │
                 │  Track, Deliver    │
                 └─────────┬──────────┘
                           │ all events
                           ▼
                 ┌────────────────────┐
                 │ NOTIFICATION CTX   │
                 │  Email, SMS, Push  │
                 └────────────────────┘

5. The Four Layers of DDD Architecture

DDD organises code into four distinct layers. Each layer has a single, clear responsibility and strict rules about what it can depend on.

┌──────────────────────────────────────────────────────────────────────┐
│                        USER INTERFACE LAYER                          │
│                                                                      │
│   REST Controllers, GraphQL Resolvers, CLI Commands, Gradio UI       │
│                                                                      │
│   Role: Accept requests from the outside world. Present results.     │
│   Rule: Contains ZERO business logic. Just translates and delegates. │
└──────────────────────────────────┬───────────────────────────────────┘
                                   │ calls
┌──────────────────────────────────▼───────────────────────────────────┐
│                      APPLICATION LAYER                               │
│                                                                      │
│   Use Cases: PlaceOrderService, CancelOrderService, GetOrderService   │
│                                                                      │
│   Role: Orchestrate a complete use case. Load objects from           │
│         repository → call domain logic → save → publish events.      │
│   Rule: NO business rules. THIN layer. Just coordination.            │
└──────────────────────────────────┬───────────────────────────────────┘
                                   │ calls
┌──────────────────────────────────▼───────────────────────────────────┐
│                    DOMAIN LAYER  ⭐ THE HEART                        │
│                                                                      │
│   Entities, Value Objects, Aggregates, Domain Events,                │
│   Domain Services, Repository Interfaces (not implementations)       │
│                                                                      │
│   Role: ALL business rules and business logic live here.             │
│   Rule: Pure Java/Kotlin — NO Spring annotations, NO JPA,            │
│         NO HTTP, NO Kafka. Zero framework dependencies.              │
└──────────────────────────────────┬───────────────────────────────────┘
                                   │ implements interfaces from Domain
┌──────────────────────────────────▼───────────────────────────────────┐
│                    INFRASTRUCTURE LAYER                              │
│                                                                      │
│   JPA Repositories, REST clients, Kafka producers/consumers,         │
│   Email services, S3, Redis, external API adapters                   │
│                                                                      │
│   Role: All technical plumbing. Databases, messaging, external APIs. │
│   Rule: Implements interfaces defined in Domain/Application layers.  │
└──────────────────────────────────────────────────────────────────────┘

DEPENDENCY RULE:
  Outer layers can depend on inner layers.
  Inner layers NEVER depend on outer layers.
  Domain layer depends on NOTHING except itself.

The Restaurant Analogy for Layers

┌─────────────────────────────────────────────────────────────┐
│  User Interface Layer  =  WAITER                            │
│    Takes order from customer, brings food back.             │
│    Does not cook. Does not decide what to cook.             │
│                                                             │
│  Application Layer     =  KITCHEN MANAGER                   │
│    Coordinates: "Table 4 needs Burger. Tell chef. Tell      │
│    cashier. Update reservation system." Pure coordination.  │
│                                                             │
│  Domain Layer          =  CHEF                              │
│    Knows ALL recipes. Enforces ALL cooking rules.           │
│    "Burger must reach 75°C internal. Never serve raw."      │
│    Contains all business knowledge.                         │
│                                                             │
│  Infrastructure Layer  =  SUPPLIERS & EQUIPMENT             │
│    Ingredients from farms. Ovens, fridges, POS systems.     │
│    Chef doesn't care which farm grows the tomatoes.         │
│    Chef just needs good tomatoes (the interface).           │
└─────────────────────────────────────────────────────────────┘

Package Structure

com.fooddelivery.orderservice/
│
├── domain/                          ← ⭐ The Heart (no framework deps)
│   ├── model/
│   │   ├── order/
│   │   │   ├── Order.java           Aggregate Root
│   │   │   ├── OrderItem.java       Entity
│   │   │   ├── OrderId.java         Value Object
│   │   │   ├── OrderStatus.java     Enum
│   │   │   └── events/
│   │   │       ├── OrderPlacedEvent.java
│   │   │       └── OrderCancelledEvent.java
│   │   └── shared/
│   │       ├── Money.java           Value Object
│   │       ├── Address.java         Value Object
│   │       └── DomainEvent.java     Interface
│   ├── service/
│   │   └── DeliveryFeePolicy.java   Domain Service
│   └── repository/
│       └── OrderRepository.java     Interface (contract only)
│
├── application/                     ← Orchestration (thin)
│   ├── PlaceOrderService.java
│   ├── CancelOrderService.java
│   └── GetOrderService.java
│
└── infrastructure/                  ← Technical plumbing
    ├── persistence/
    │   └── JpaOrderRepository.java  Implements OrderRepository
    ├── messaging/
    │   └── KafkaEventPublisher.java
    └── web/
        └── OrderController.java

6. Entities

What is an Entity?

An Entity is a domain object that has a unique identity that remains constant for its entire lifetime, even as its other attributes change.

Real-world analogy: You are an Entity. You have an Aadhaar number (identity) that never changes. You can change your name, address, weight, job, phone number — you are still the same person because your Aadhaar number identifies you uniquely.

Identity vs Value Thinking

ENTITY — the IDENTITY makes it unique:
  ─────────────────────────────────────────────────────
  Order #5001 — even if you add/remove items, change
  the address, or update its status, it is ALWAYS
  Order #5001. The ID is what matters.

  Customer "Priya" with ID CUST-999 — even if she
  changes her phone number or moves to a new city,
  she is STILL CUST-999. Same account. Same history.

VALUE OBJECT — the VALUES make it unique:
  ─────────────────────────────────────────────────────
  ₹200 — You don't care WHICH ₹200 note you hold.
  Any ₹200 is as good as any other ₹200.
  No identity needed.

  "123 MG Road, Pune" — Just a data point.
  No tracking needed. Two addresses with same
  values are completely interchangeable.

Entity Characteristics

┌────────────────────────────────────────────────────────┐
│                 ENTITY CHARACTERISTICS                 │
│                                                        │
│  ✅ Has a UNIQUE ID (OrderId, CustomerId, etc.)         │
│  ✅ Two entities with same ID = the SAME entity         │
│  ✅ MUTABLE — attributes change over time               │
│  ✅ Has a LIFECYCLE (PLACED → CONFIRMED → DELIVERED)    │
│  ✅ Contains BUSINESS BEHAVIOUR — not just data         │
│  ✅ PROTECTS invariants — throws exception on bad state │
│  ❌ NO public setters — state changes via named methods │
│  ❌ NEVER expose internal collections directly          │
└────────────────────────────────────────────────────────┘

Entity — Full Java Example

java
// domain/model/order/Order.java

public class Order {

    // ══════════════════════════════════════════
    // IDENTITY — assigned at creation, never changes
    // ══════════════════════════════════════════
    private final OrderId id;

    // ══════════════════════════════════════════
    // ATTRIBUTES — can change over lifecycle
    // ══════════════════════════════════════════
    private final CustomerId    customerId;     // Ref by ID only — NOT a Customer object
    private final RestaurantId  restaurantId;   // Ref by ID only — NOT a Restaurant object
    private List<OrderItem>     orderItems;
    private Address             deliveryAddress; // Value Object
    private OrderStatus         status;
    private Money               totalAmount;     // Value Object
    private LocalDateTime       placedAt;
    private String              specialInstructions;

    // Domain Events collected during operations — published later
    private final List<DomainEvent> domainEvents = new ArrayList<>();

    // ══════════════════════════════════════════
    // FACTORY METHOD — the only way to create a valid Order
    // ══════════════════════════════════════════
    public static Order place(
            CustomerId customerId,
            RestaurantId restaurantId,
            List<OrderItemRequest> itemRequests,
            Address deliveryAddress) {

        // Business rule: Must have at least one item
        if (itemRequests == null || itemRequests.isEmpty()) {
            throw new EmptyOrderException("An order must contain at least one item");
        }

        OrderId id = OrderId.generate();
        Order order = new Order(id, customerId, restaurantId, deliveryAddress);

        for (OrderItemRequest req : itemRequests) {
            order.orderItems.add(new OrderItem(
                MenuItemId.of(req.menuItemId()),
                req.name(),
                Money.of(req.unitPrice()),
                Quantity.of(req.quantity())
            ));
        }

        order.recalculateTotal();
        order.status    = OrderStatus.PLACED;
        order.placedAt  = LocalDateTime.now();
        order.domainEvents.add(
            new OrderPlacedEvent(id, customerId, restaurantId, order.totalAmount)
        );

        return order;
    }

    // ══════════════════════════════════════════
    // BUSINESS OPERATIONS — each enforces its own rules
    // ══════════════════════════════════════════

    /** Restaurant has accepted this order and will prepare it. */
    public void confirmByRestaurant() {
        requireStatus(OrderStatus.PLACED, "confirm");
        this.status = OrderStatus.CONFIRMED;
        domainEvents.add(new OrderConfirmedEvent(this.id, this.restaurantId));
    }

    /** Kitchen has finished cooking — food is ready for pickup. */
    public void markReadyForPickup() {
        requireStatus(OrderStatus.CONFIRMED, "mark ready for pickup");
        this.status = OrderStatus.READY_FOR_PICKUP;
        domainEvents.add(new OrderReadyEvent(this.id));
    }

    /** Delivery partner has collected the food and is on the way. */
    public void dispatch(DeliveryPartnerId partnerId) {
        requireStatus(OrderStatus.READY_FOR_PICKUP, "dispatch");
        Objects.requireNonNull(partnerId, "Delivery partner required for dispatch");
        this.status = OrderStatus.ON_THE_WAY;
        domainEvents.add(new OrderDispatchedEvent(this.id, partnerId));
    }

    /** Food has been physically handed to the customer. */
    public void deliver() {
        requireStatus(OrderStatus.ON_THE_WAY, "deliver");
        this.status = OrderStatus.DELIVERED;
        domainEvents.add(new OrderDeliveredEvent(this.id, this.customerId));
    }

    /**
     * Cancel the order.
     * Business rule: Cannot cancel once the order is ON_THE_WAY or DELIVERED.
     */
    public void cancel(CancellationReason reason) {
        if (status == OrderStatus.ON_THE_WAY || status == OrderStatus.DELIVERED) {
            throw new OrderNotCancellableException(
                "Cannot cancel order " + id + " — already " + status.displayName() +
                ". Please contact support for assistance."
            );
        }
        this.status = OrderStatus.CANCELLED;
        domainEvents.add(new OrderCancelledEvent(this.id, this.customerId, reason));
    }

    // ══════════════════════════════════════════
    // PRIVATE HELPERS
    // ══════════════════════════════════════════
    private void requireStatus(OrderStatus required, String action) {
        if (this.status != required) {
            throw new InvalidOrderStateException(
                "Cannot " + action + " — order is " + status + ", expected " + required
            );
        }
    }

    private void recalculateTotal() {
        this.totalAmount = orderItems.stream()
            .map(OrderItem::subtotal)
            .reduce(Money.ZERO, Money::add);
    }

    // ══════════════════════════════════════════
    // IDENTITY-BASED EQUALITY — Entities are equal when IDs match
    // ══════════════════════════════════════════
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Order other)) return false;
        return id.equals(other.id);
    }

    @Override
    public int hashCode() { return id.hashCode(); }

    // ══════════════════════════════════════════
    // GETTERS — no setters, state changes via methods only
    // ══════════════════════════════════════════
    public OrderId getId()           { return id; }
    public OrderStatus getStatus()   { return status; }
    public Money getTotalAmount()    { return totalAmount; }
    public CustomerId getCustomerId(){ return customerId; }
    public List<OrderItem> getOrderItems() {
        return Collections.unmodifiableList(orderItems); // Never expose mutable list
    }
    public List<DomainEvent> getDomainEvents() {
        return Collections.unmodifiableList(domainEvents);
    }
    public void clearDomainEvents() { domainEvents.clear(); }

    private Order(OrderId id, CustomerId customerId,
                  RestaurantId restaurantId, Address deliveryAddress) {
        this.id              = Objects.requireNonNull(id);
        this.customerId      = Objects.requireNonNull(customerId);
        this.restaurantId    = Objects.requireNonNull(restaurantId);
        this.deliveryAddress = Objects.requireNonNull(deliveryAddress);
        this.orderItems      = new ArrayList<>();
        this.status          = OrderStatus.DRAFT;
    }
}

7. Value Objects

What is a Value Object?

A Value Object has no identity. It is defined entirely by its values. Two Value Objects with identical values are completely interchangeable. They are always immutable — once created, they never change.

Real-world analogy: A ₹100 note. You don't care WHICH specific note you hold. Any ₹100 = any other ₹100. If you want ₹200, you don't edit the note — you get a NEW ₹200. That's how Value Objects work: replace, never mutate.

Value Object Rules

┌────────────────────────────────────────────────────────┐
│                VALUE OBJECT RULES                      │
│                                                        │
│  ✅ No unique ID                                       │
│  ✅ Compared by ALL VALUES (not reference or ID)       │
│  ✅ IMMUTABLE — private final fields, no setters       │
│  ✅ SELF-VALIDATING — validates in constructor          │
│  ✅ REPLACED not updated (create new instance)         │
│  ✅ Can have BEHAVIOUR (add, subtract, format)         │
│  ✅ mark the class as FINAL                            │
│                                                        │
│  Common examples:                                      │
│  Money, Address, Email, PhoneNumber, Quantity,         │
│  DateRange, GPS Coordinates, OrderId (typed ID)        │
└────────────────────────────────────────────────────────┘

Value Objects — Java Examples

java
// ═══════════════════════════════════════════════════════════
// domain/model/shared/Money.java
// ═══════════════════════════════════════════════════════════
public final class Money {

    public static final Money ZERO = new Money(BigDecimal.ZERO, "INR");

    private final BigDecimal amount;
    private final String     currency;

    public Money(BigDecimal amount, String currency) {
        if (amount == null)
            throw new IllegalArgumentException("Amount cannot be null");
        if (currency == null || currency.isBlank())
            throw new IllegalArgumentException("Currency cannot be blank");
        if (amount.compareTo(BigDecimal.ZERO) < 0)
            throw new IllegalArgumentException("Money amount cannot be negative: " + amount);
        // Store with consistent scale
        this.amount   = amount.setScale(2, RoundingMode.HALF_UP);
        this.currency = currency.toUpperCase();
    }

    public static Money of(double amount)    { return new Money(BigDecimal.valueOf(amount), "INR"); }
    public static Money ofINR(BigDecimal a)  { return new Money(a, "INR"); }

    // ── Behaviour — always returns NEW Money ─────────────
    public Money add(Money other) {
        assertSameCurrency(other);
        return new Money(this.amount.add(other.amount), this.currency);
    }

    public Money subtract(Money other) {
        assertSameCurrency(other);
        BigDecimal result = this.amount.subtract(other.amount);
        if (result.compareTo(BigDecimal.ZERO) < 0)
            throw new InsufficientAmountException("Cannot subtract " + other + " from " + this);
        return new Money(result, this.currency);
    }

    public Money multiplyBy(int factor) {
        return new Money(this.amount.multiply(BigDecimal.valueOf(factor)), this.currency);
    }

    public Money applyDiscountPercent(int percent) {
        BigDecimal factor = BigDecimal.ONE
            .subtract(BigDecimal.valueOf(percent).divide(BigDecimal.valueOf(100)));
        return new Money(this.amount.multiply(factor), this.currency);
    }

    public boolean isGreaterThan(Money other) {
        assertSameCurrency(other);
        return this.amount.compareTo(other.amount) > 0;
    }

    public boolean isZero() { return amount.compareTo(BigDecimal.ZERO) == 0; }

    // ── Equality by VALUE ─────────────────────────────────
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money m)) return false;
        return amount.compareTo(m.amount) == 0 && currency.equals(m.currency);
    }

    @Override public int hashCode() { return Objects.hash(amount.stripTrailingZeros(), currency); }
    @Override public String toString() { return "₹" + amount.toPlainString(); }

    private void assertSameCurrency(Money other) {
        if (!currency.equals(other.currency))
            throw new CurrencyMismatchException(currency + " vs " + other.currency);
    }

    public BigDecimal getAmount() { return amount; }
    public String getCurrency()   { return currency; }
}


// ═══════════════════════════════════════════════════════════
// domain/model/shared/Address.java
// ═══════════════════════════════════════════════════════════
public final class Address {

    private final String flatOrHouseNo;
    private final String streetOrLocality;
    private final String city;
    private final String state;
    private final String pinCode;

    public Address(String flatOrHouseNo, String streetOrLocality,
                   String city, String state, String pinCode) {
        this.flatOrHouseNo    = requireNonBlank(flatOrHouseNo,    "Flat/House No");
        this.streetOrLocality = requireNonBlank(streetOrLocality, "Street/Locality");
        this.city             = requireNonBlank(city,             "City");
        this.state            = requireNonBlank(state,            "State");
        this.pinCode          = validatePin(pinCode);
    }

    private static String validatePin(String pin) {
        if (pin == null || !pin.matches("\\d{6}"))
            throw new InvalidAddressException("PIN code must be exactly 6 digits, got: " + pin);
        return pin;
    }

    private static String requireNonBlank(String value, String fieldName) {
        if (value == null || value.isBlank())
            throw new InvalidAddressException(fieldName + " cannot be blank");
        return value.trim();
    }

    public String singleLine() {
        return flatOrHouseNo + ", " + streetOrLocality + ", "
             + city + ", " + state + " - " + pinCode;
    }

    // ── Equality by ALL VALUES ─────────────────────────────
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Address a)) return false;
        return Objects.equals(flatOrHouseNo,    a.flatOrHouseNo)
            && Objects.equals(streetOrLocality, a.streetOrLocality)
            && Objects.equals(city,             a.city)
            && Objects.equals(state,            a.state)
            && Objects.equals(pinCode,          a.pinCode);
    }

    @Override public int hashCode() {
        return Objects.hash(flatOrHouseNo, streetOrLocality, city, state, pinCode);
    }

    public String getCity()    { return city; }
    public String getPinCode() { return pinCode; }
    public String getState()   { return state; }
}


// ═══════════════════════════════════════════════════════════
// domain/model/order/OrderId.java — Typed ID as Value Object
// ═══════════════════════════════════════════════════════════
public final class OrderId {

    private final String value;

    private OrderId(String value) {
        if (value == null || value.isBlank())
            throw new IllegalArgumentException("OrderId cannot be blank");
        this.value = value;
    }

    public static OrderId generate() {
        return new OrderId("ORD-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
    }

    public static OrderId of(String value) { return new OrderId(value); }

    @Override public boolean equals(Object o) {
        if (!(o instanceof OrderId other)) return false;
        return value.equals(other.value);
    }

    @Override public int hashCode() { return value.hashCode(); }
    @Override public String toString() { return value; }
}

Entity vs Value Object — Side by Side

┌──────────────────────────────┬──────────────────────────────┐
│           ENTITY             │        VALUE OBJECT          │
├──────────────────────────────┼──────────────────────────────┤
│ Has a unique ID              │ No identity at all           │
│ Compared by ID               │ Compared by all values       │
│ Mutable over time            │ Immutable — never changes    │
│ Has a lifecycle              │ Created and discarded        │
│                              │                              │
│ Order, Customer, Restaurant, │ Money, Address, Quantity,    │
│ DeliveryPartner              │ OrderId, Email, PhoneNumber  │
├──────────────────────────────┼──────────────────────────────┤
│ Order #5001 stays #5001      │ ₹100 is ₹100 — no matter    │
│ even if its items change.    │ which note. Replaceable.     │
└──────────────────────────────┴──────────────────────────────┘

8. Aggregates & Aggregate Root

What is an Aggregate?

An Aggregate is a cluster of Entities and Value Objects that must be kept consistent together. It has one designated leader called the Aggregate Root — the single entry point for ALL changes inside the cluster.

Real-world analogy: An Order at a food delivery app is an Aggregate.
You cannot directly call the kitchen and change the burger quantity.
You must tell the WAITER (Aggregate Root) who updates the order.
The waiter ensures the total is recalculated, the status is valid, and all rules are followed.
The waiter is the gatekeeper.

The Consistency Problem — Why Aggregates Exist

WITHOUT AGGREGATES (Chaos):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Thread A: Adds item ₹200 → updates total to ₹700
  Thread B: Removes item → updates total to ₹300
  Thread C: Reads total = ??? (inconsistent data!)

  Payment service charges ₹300
  Delivery service calculates for ₹700 order
  DATA CORRUPTION → Customer overcharged or undercharged

WITH AGGREGATES (Safety):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  ALL changes go through Order (Aggregate Root).
  Order.addItem() automatically recalculates total.
  One transaction, one aggregate, one consistent state.
  Total is ALWAYS equal to sum of items. Guaranteed.

The Order Aggregate — Visual Diagram

┌──────────────────────────────────────────────────────────────────────┐
│                         ORDER AGGREGATE                              │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │                ORDER  (Aggregate Root ⭐)                     │   │
│  │                                                              │   │
│  │  id:           ORD-A1B2C3D4      ← Unique identity           │   │
│  │  customerId:   CUST-99           ← Reference by ID only!     │   │
│  │  restaurantId: REST-12           ← Reference by ID only!     │   │
│  │  status:       PLACED                                        │   │
│  │  totalAmount:  ₹499.00           ← Auto-maintained           │   │
│  │  placedAt:     2025-04-25 14:32                              │   │
│  │                                                              │   │
│  │  place()  confirmByRestaurant()  cancel()  deliver()         │   │
│  │       ↑ The ONLY gate into this aggregate from outside       │   │
│  └──────────────┬─────────────────────────────┬────────────────┘   │
│                 │ owns                         │ owns               │
│  ┌──────────────▼──────────────────┐ ┌────────▼──────────────────┐ │
│  │   ORDER ITEMS (Entities)        │ │  DELIVERY ADDRESS         │ │
│  │                                 │ │  (Value Object)           │ │
│  │  Item 1: Veg Burger × 2         │ │                           │ │
│  │           ₹150 each = ₹300      │ │  B-12, FC Road,           │ │
│  │                                 │ │  Pune - 411005            │ │
│  │  Item 2: Masala Fries × 1       │ │                           │ │
│  │           ₹99 = ₹99             │ │  Immutable. If address    │ │
│  │                                 │ │  changes, create a new    │ │
│  │  Item 3: Pepsi × 2              │ │  Address object.          │ │
│  │           ₹50 each = ₹100       │ └───────────────────────────┘ │
│  │                                 │                               │
│  │  ← Cannot be modified directly  │                               │
│  │    Must go through Order root   │                               │
│  └─────────────────────────────────┘                               │
│                                                                      │
│  AGGREGATE BOUNDARY: The dashed outer box.                          │
│  Nothing outside can directly touch OrderItems.                     │
│  All access must pass through Order (the root).                     │
└──────────────────────────────────────────────────────────────────────┘

The Four Golden Rules of Aggregates

╔════════════════════════════════════════════════════════════════╗
║  RULE 1: Reference Other Aggregates by ID ONLY                ║
╠════════════════════════════════════════════════════════════════╣
║  ❌ class Order { Customer customer; }    // Object reference  ║
║  ✅ class Order { CustomerId customerId; }// ID reference      ║
║                                                                ║
║  Why? Object reference creates a dependency. If Customer       ║
║  changes, Order breaks. With an ID, they are independent.     ║
╠════════════════════════════════════════════════════════════════╣
║  RULE 2: One Transaction Modifies One Aggregate               ║
╠════════════════════════════════════════════════════════════════╣
║  ❌ One @Transactional saves Order AND Inventory              ║
║  ✅ Save Order → emit OrderPlacedEvent → Inventory reacts     ║
║                                                                ║
║  Why? Small transactions are fast, safe, and easy to reason   ║
║  about. Use Domain Events for cross-aggregate updates.        ║
╠════════════════════════════════════════════════════════════════╣
║  RULE 3: External Access Only Through the Aggregate Root      ║
╠════════════════════════════════════════════════════════════════╣
║  ❌ orderItemRepo.updateQuantity(itemId, newQty)               ║
║  ✅ order.updateItemQuantity(itemId, Quantity.of(newQty))      ║
║                                                                ║
║  Why? The root enforces all business rules. Direct access     ║
║  bypasses validation and creates inconsistent state.          ║
╠════════════════════════════════════════════════════════════════╣
║  RULE 4: Keep Aggregates SMALL                                ║
╠════════════════════════════════════════════════════════════════╣
║  Include ONLY what MUST change together in one transaction.   ║
║                                                                ║
║  ❌ God Aggregate: Order + Customer + Restaurant + Driver      ║
║  ✅ Small Aggregate: Order + OrderItems + DeliveryAddress      ║
║                                                                ║
║  Clue: If it doesn't need to be consistent with the root,     ║
║  it probably belongs in a different aggregate.                ║
╚════════════════════════════════════════════════════════════════╝

9. Domain Events

What is a Domain Event?

A Domain Event is a record of something significant that happened in your business domain — always stated in past tense. It is a fact. It happened. It cannot be undone.

Real-world analogy: At an airport, when a flight LANDS, an "Aircraft Landed" event occurs. Independently:

  • The refuelling crew reacts by scheduling the fuel truck.
  • The cleaning crew reacts by heading to the gate.
  • The gate agent reacts by preparing for the next flight.
  • Baggage handlers react by going to baggage claim.

Nobody told each team individually. The event told them all. Each reacted independently. This is exactly how Domain Events work in software.

Command vs Domain Event

COMMAND (a request — might be rejected):              DOMAIN EVENT (a fact — already happened):
──────────────────────────────────────────            ─────────────────────────────────────────────
PlaceOrder                                            OrderPlaced
CancelOrder                                           OrderCancelled
DispatchOrder                                         OrderDispatched

• Future/present tense                                • Past tense
• Addressed to ONE specific handler                   • Published to MANY interested listeners
• Can FAIL — business can reject it                   • Cannot fail — it already happened
• "Please do this for me"                             • "This happened — react if you care"

Domain Events — Full Flow Diagram

Customer submits order
         │
         ▼
   Order.place() is called
         │
         ▼
  Order creates OrderPlacedEvent  ←─────────────────────────────┐
         │                                                       │
         ▼                                                       │
  orderRepository.save(order)                                   │
         │                                                       │
         ▼                                                       │
  DomainEventPublisher publishes to Kafka topic "order.placed"  │
         │                                                       │
         ├──────────────────────────────────────────────────────┘
         │
         ├──────────────────────────────────┐
         │                                  │
         ▼                                  ▼
PAYMENT SERVICE hears it:          RESTAURANT SERVICE hears it:
  → Authorise ₹499 payment           → Push notification to restaurant
  → Fires PaymentAuthorisedEvent     → "New order! Start preparing."
         │                                  │
         └──────────────┬───────────────────┘
                        │ (both events)
                        ▼
              NOTIFICATION SERVICE hears it:
              → Sends: "Order #ORD-A1B2 placed!
                        Payment ₹499 received.
                        Estimated delivery: 35 mins."
              → SMS + Email + Push notification

Domain Events — Java Code

java
// ═════════════════════════════════════════════════════════
// domain/model/shared/DomainEvent.java — Marker Interface
// ═════════════════════════════════════════════════════════
public interface DomainEvent {
    String getEventId();
    LocalDateTime getOccurredAt();
    String getEventType();
}


// ═════════════════════════════════════════════════════════
// domain/model/order/events/OrderPlacedEvent.java
// ═════════════════════════════════════════════════════════
public class OrderPlacedEvent implements DomainEvent {

    private final String        eventId;
    private final OrderId       orderId;
    private final CustomerId    customerId;
    private final RestaurantId  restaurantId;
    private final Money         totalAmount;
    private final LocalDateTime occurredAt;

    public OrderPlacedEvent(OrderId orderId, CustomerId customerId,
                             RestaurantId restaurantId, Money totalAmount) {
        this.eventId      = UUID.randomUUID().toString();
        this.orderId      = orderId;
        this.customerId   = customerId;
        this.restaurantId = restaurantId;
        this.totalAmount  = totalAmount;
        this.occurredAt   = LocalDateTime.now();
    }

    @Override public String getEventId()       { return eventId; }
    @Override public LocalDateTime getOccurredAt() { return occurredAt; }
    @Override public String getEventType()     { return "ORDER_PLACED"; }

    public OrderId      getOrderId()      { return orderId; }
    public CustomerId   getCustomerId()   { return customerId; }
    public RestaurantId getRestaurantId() { return restaurantId; }
    public Money        getTotalAmount()  { return totalAmount; }
}

10. Repositories

What is a Repository?

A Repository is an abstraction that gives your domain layer the illusion of working with an in-memory collection of domain objects. It completely hides how objects are stored.

Real-world analogy: A librarian. You say: "Give me all books about Python published after 2020." You don't care if books are on wooden shelves, digital archives, or in a warehouse in another city. The librarian handles storage details. You just get the books.

Repository — Interface (Domain Layer)

java
// domain/repository/OrderRepository.java

/**
 * Contract for Order persistence.
 * This interface lives in the DOMAIN layer.
 * It speaks the domain language — no SQL, no JPA, no technical details.
 */
public interface OrderRepository {

    void           save(Order order);
    void           delete(OrderId orderId);

    Optional<Order> findById(OrderId orderId);
    List<Order>     findByCustomerId(CustomerId customerId);
    List<Order>     findByRestaurantId(RestaurantId restaurantId);
    List<Order>     findByStatus(OrderStatus status);
    List<Order>     findActiveOrdersForDeliveryPartner(DeliveryPartnerId partnerId);
    boolean         existsById(OrderId orderId);
}

Repository — Implementation (Infrastructure Layer)

java
// infrastructure/persistence/JpaOrderRepository.java

@Repository
public class JpaOrderRepository implements OrderRepository {

    private final SpringDataOrderJpaRepo jpaRepo;
    private final OrderPersistenceMapper mapper;

    @Override
    public void save(Order order) {
        OrderJpaEntity entity = mapper.toJpa(order);
        jpaRepo.save(entity);
    }

    @Override
    public Optional<Order> findById(OrderId orderId) {
        return jpaRepo.findById(orderId.toString())
                      .map(mapper::toDomain);   // Convert JPA entity → domain object
    }

    @Override
    public List<Order> findByCustomerId(CustomerId customerId) {
        return jpaRepo.findByCustomerId(customerId.toString())
                      .stream()
                      .map(mapper::toDomain)
                      .toList();
    }

    // ... remaining methods
}

// Spring Data JPA interface — infrastructure detail only
interface SpringDataOrderJpaRepo extends JpaRepository<OrderJpaEntity, String> {
    List<OrderJpaEntity> findByCustomerId(String customerId);
    List<OrderJpaEntity> findByStatus(String status);
}

11. Domain Services

What is a Domain Service?

A Domain Service contains business logic that does not naturally belong to a single Entity or Value Object — it operates across multiple domain objects and represents a real business concept.

Real-world analogy: A referee in cricket. When there's a dispute (was it out or not?), you don't ask the batsman or the bowler — that's biased. The REFEREE applies the rules across both players. The referee is the Domain Service.

When to Use a Domain Service

USE a Domain Service WHEN:
  ✅ Logic involves MULTIPLE aggregates
  ✅ The operation has a meaningful BUSINESS NAME
  ✅ It would feel WRONG to put it on any one entity

DO NOT use Domain Service for:
  ❌ Logic that belongs to ONE entity → put it on the entity
  ❌ Orchestration (load, call, save) → Application Service
  ❌ Database or email  → Infrastructure

Domain Service — Java Example

java
// domain/service/DeliveryFeePolicy.java

/**
 * Calculates delivery fee for an order.
 * This is a Domain Service because it operates across
 * Order, Customer, Restaurant, and distance — no single
 * entity is the right home for this logic.
 */
public class DeliveryFeePolicy {

    /**
     * Business Rules (as agreed with Operations Manager):
     *  1. Swiggy One members always get free delivery
     *  2. Free delivery if subtotal > ₹299
     *  3. Base fee ₹20 for 0–3 km
     *  4. +₹6 per km beyond 3 km
     *  5. Premium restaurants add ₹30 surcharge
     *  6. Maximum delivery fee is ₹80 (cap)
     */
    public Money calculate(Order order, Customer customer,
                            Restaurant restaurant, DistanceKm distance) {
        // Rule 1: Subscription members always free
        if (customer.hasActiveSubscription(SubscriptionType.SWIGGY_ONE)) {
            return Money.ZERO;
        }

        // Rule 2: Free above threshold
        if (order.getTotalAmount().isGreaterThan(Money.of(299))) {
            return Money.ZERO;
        }

        // Rule 3 & 4: Distance-based fee
        Money fee;
        if (distance.isWithin(3)) {
            fee = Money.of(20);
        } else {
            int extraKm = distance.getValue() - 3;
            fee = Money.of(20).add(Money.of(6 * extraKm));
        }

        // Rule 5: Premium restaurant surcharge
        if (restaurant.isPremium()) {
            fee = fee.add(Money.of(30));
        }

        // Rule 6: Cap at ₹80
        Money cap = Money.of(80);
        return fee.isGreaterThan(cap) ? cap : fee;
    }
}

12. Application Services

What is an Application Service?

An Application Service is the orchestrator of a use case. It is deliberately thin — it loads domain objects, calls domain logic, saves results, and publishes events. It contains zero business rules.

Real-world analogy: An event manager at a wedding. They don't cook, decorate, or perform music. They coordinate: "Tell caterers to start. Inform DJ the couple has arrived. Seat the guests. Organise photographer." The specialists do the actual work. The event manager orchestrates.

Application Service = Pure Coordination
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  1. Receive a COMMAND (PlaceOrderCommand)
  2. LOAD domain objects from repository
  3. CALL domain logic (entity methods, domain services)
  4. SAVE changes back to repository
  5. PUBLISH domain events
  6. RETURN a result to the caller

  ❌ No if/else business conditions
  ❌ No money calculations
  ❌ No direct database queries
  ❌ No sending emails directly

Application Service — Java Example

java
// application/PlaceOrderService.java

@Service
@Transactional
public class PlaceOrderService {

    private final OrderRepository      orderRepository;
    private final CustomerRepository   customerRepository;
    private final RestaurantRepository restaurantRepository;
    private final DeliveryFeePolicy    deliveryFeePolicy;
    private final DomainEventPublisher eventPublisher;

    // ── Input Command ──────────────────────────────────────
    public record PlaceOrderCommand(
        String customerId,
        String restaurantId,
        String flatNo, String street, String city, String state, String pinCode,
        List<ItemRequest> items
    ) {
        public record ItemRequest(String menuItemId, String name, double price, int qty) {}
    }

    // ── Output Result ─────────────────────────────────────
    public record PlaceOrderResult(
        String orderId,
        double subtotal,
        double deliveryFee,
        double grandTotal
    ) {}

    // ── The Use Case ──────────────────────────────────────
    public PlaceOrderResult execute(PlaceOrderCommand cmd) {

        // 1. LOAD domain objects
        Customer customer = customerRepository
            .findById(CustomerId.of(cmd.customerId()))
            .orElseThrow(() -> new CustomerNotFoundException(cmd.customerId()));

        Restaurant restaurant = restaurantRepository
            .findById(RestaurantId.of(cmd.restaurantId()))
            .orElseThrow(() -> new RestaurantNotFoundException(cmd.restaurantId()));

        // 2. DOMAIN VALIDATION (domain object decides)
        if (!restaurant.isAcceptingOrders()) {
            throw new RestaurantNotAvailableException(
                restaurant.getName() + " is not accepting orders right now."
            );
        }

        // 3. BUILD value objects
        Address deliveryAddress = new Address(
            cmd.flatNo(), cmd.street(), cmd.city(), cmd.state(), cmd.pinCode()
        );

        List<OrderItemRequest> itemRequests = cmd.items().stream()
            .map(i -> new OrderItemRequest(i.menuItemId(), i.name(), i.price(), i.qty()))
            .toList();

        // 4. CREATE aggregate — domain logic runs inside here
        Order order = Order.place(
            customer.getId(),
            restaurant.getId(),
            itemRequests,
            deliveryAddress
        );

        // 5. DOMAIN SERVICE for cross-entity logic
        DistanceKm distance = restaurant.distanceTo(deliveryAddress);
        Money deliveryFee   = deliveryFeePolicy.calculate(order, customer, restaurant, distance);

        // 6. SAVE
        orderRepository.save(order);

        // 7. PUBLISH events
        eventPublisher.publish(order.getDomainEvents());
        order.clearDomainEvents();

        // 8. RETURN plain result DTO
        Money grandTotal = order.getTotalAmount().add(deliveryFee);
        return new PlaceOrderResult(
            order.getId().toString(),
            order.getTotalAmount().getAmount().doubleValue(),
            deliveryFee.getAmount().doubleValue(),
            grandTotal.getAmount().doubleValue()
        );
    }
}

13. Factories

What is a Factory?

A Factory encapsulates complex object creation logic. When creating an Aggregate requires more than simple new, move that logic to a Factory.

java
// domain/service/OrderFactory.java

public class OrderFactory {

    /**
     * Creates an Order directly from a Customer's saved Cart.
     * Encapsulates the conversion logic so it's not duplicated.
     */
    public Order createFromCart(Cart cart, Address deliveryAddress) {
        if (cart.isEmpty()) {
            throw new EmptyCartException("Cannot create order from empty cart");
        }

        List<OrderItemRequest> requests = cart.getItems().stream()
            .map(item -> new OrderItemRequest(
                item.getMenuItemId().toString(),
                item.getName(),
                item.getUnitPrice().getAmount().doubleValue(),
                item.getQuantity().getValue()
            )).toList();

        return Order.place(
            cart.getCustomerId(),
            cart.getRestaurantId(),
            requests,
            deliveryAddress
        );
    }
}

14. Context Mapping

What is Context Mapping?

Context Mapping documents how different Bounded Contexts relate to and communicate with each other. It is the "foreign relations" map of your system.

Key Context Mapping Patterns

┌─────────────────────────────────────────────────────────────────────┐
│                   CONTEXT MAPPING PATTERNS                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  CUSTOMER-SUPPLIER                                                  │
│  Order Context (Downstream) consumes Menu Context (Upstream).       │
│  Upstream provides, downstream consumes.                            │
│  Menu Context ──────────────────▶ Order Context                    │
│                                                                     │
│  ANTI-CORRUPTION LAYER (ACL)                                        │
│  Protect your clean domain model from a messy external system.      │
│  Translate between models at the boundary.                          │
│  Razorpay API ──[ACL Translator]──▶ Your PaymentGateway interface  │
│                                                                     │
│  OPEN HOST SERVICE                                                  │
│  Publish a well-defined, stable API for many consumers.             │
│  Order Service ──[REST API / Kafka Events]──▶ Many Services         │
│                                                                     │
│  CONFORMIST                                                         │
│  You have no power to change the upstream.                          │
│  You adopt their model as-is (e.g., a government tax API).          │
│                                                                     │
│  SHARED KERNEL                                                      │
│  Two closely-related teams share a small, common model.             │
│  Changes require coordination between both teams.                   │
└─────────────────────────────────────────────────────────────────────┘

Anti-Corruption Layer — Java Example

java
// Translating Razorpay's messy API into your clean domain language

// infrastructure/adapter/out/payment/RazorpayAdapter.java

@Component
public class RazorpayAdapter implements PaymentGateway {  // PaymentGateway is YOUR interface

    private final RazorpayApiClient razorpayClient;

    @Override
    public PaymentResult charge(Order order, PaymentDetails payment) {

        // Translate YOUR domain → Razorpay format (Anti-Corruption)
        RazorpayChargeRequest razorReq = new RazorpayChargeRequest(
            order.getTotalAmount().getAmount()
                 .multiply(BigDecimal.valueOf(100)).intValue(), // They want paise!
            "inr",
            order.getId().toString(),
            payment.getToken()
        );

        // Call external API
        RazorpayChargeResponse razorResp = razorpayClient.charge(razorReq);

        // Translate Razorpay format → YOUR domain (Anti-Corruption)
        if ("captured".equals(razorResp.getStatus())) {
            return PaymentResult.success(PaymentReference.of(razorResp.getPaymentId()));
        }
        return PaymentResult.failed(razorResp.getError().getDescription());
    }
}

15. Strategic vs Tactical DDD

┌─────────────────────────────────────────────────────────────────┐
│                      STRATEGIC DDD                              │
│                                                                 │
│  The big picture. Answers the question:                         │
│  "How do we structure our entire system?"                       │
│                                                                 │
│  Tools: Domain, Subdomains, Bounded Contexts, Context Maps      │
│                                                                 │
│  Questions:                                                     │
│  • What are the different areas of our business?               │
│  • Which is our core competitive advantage?                    │
│  • How do different parts communicate?                         │
│  • What do we build vs buy?                                    │
│                                                                 │
│  Who: Architects + Tech Leads + Business Leaders               │
│  When: BEFORE writing any code                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                       TACTICAL DDD                              │
│                                                                 │
│  The implementation detail. Answers the question:               │
│  "How do we model this Bounded Context in code?"                │
│                                                                 │
│  Tools: Entities, Value Objects, Aggregates, Repositories,      │
│         Domain Events, Domain Services, Application Services,   │
│         Factories                                               │
│                                                                 │
│  Questions:                                                     │
│  • Where does this business rule live?                         │
│  • How do we keep this data consistent?                        │
│  • How do parts communicate without tight coupling?            │
│                                                                 │
│  Who: Developers + Domain Experts (pair programming)           │
│  When: Inside each Bounded Context                              │
└─────────────────────────────────────────────────────────────────┘