Chuyển đến nội dung chính

Lesson 2: Domain-Driven Design — System separation thinking

DDD Platform: Ubiquitous Language, Bounded Context, Aggregates, Domain Events. How to use Event Storming to discover domains. Strategic vs Tactical DDD and application to Microservices & Micro Frontend division.

🏗️ Architecture — Lesson 2 Lesson 2: Domain-Driven Design — Divided thinking system separation

Microservices & Micro Frontend system design — From basics to Production

Part 1: Foundation — Evolution of Architecture

xdev.asia

Introduction

Domain-Driven Design (DDD) is more than just a method of writing code — it's thinking that decomposes complex systems into parts that make business sense. DDD is the foundation that decides how to divide Microservices and how to divide Micro Frontend. If divided incorrectly, you will have a "distributed monolith" — worse than the original monolith.

Context Map — Bounded Contexts and relationships between domains in DDD


1. Why is DDD needed?

1.1 The problem of technical decomposition

Dividing the system into technical layers (UI layer, Business layer, Data layer) is the most common mistake:

❌ Technical Decomposition (SAI):
├── frontend-service
├── backend-service
├── database-service
└── notification-service

✅ Domain Decomposition (ĐÚNG):
├── user-management (UI + API + DB cho User)
├── product-catalog (UI + API + DB cho Product)
├── order-processing (UI + API + DB cho Order)
└── payment (UI + API + DB cho Payment)

1.2 What does DDD solve?

  • Ubiquitous Language: The whole team (dev, PM, business) speaks the same "language"
  • Bounded Context: Define clear boundaries between domains
  • Reduce coupling: Each bounded context is an independent unit
  • Align business & tech: Architecture reflects business structure

2. Strategic DDD — Look at the big picture

2.1 Ubiquitous Language

Each Bounded Context has its own language. Same word "Product" but different meaning in each context:

┌───────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│  Product Catalog   │  │   Order Context  │  │  Shipping Context│
│                    │  │                  │  │                  │
│ Product:           │  │ OrderItem:       │  │ Parcel:          │
│ - name, desc       │  │ - productId      │  │ - weight         │
│ - price, images    │  │ - quantity       │  │ - dimensions     │
│ - categories       │  │ - unitPrice      │  │ - tracking       │
│ - reviews          │  │ - discount       │  │ - destination    │
└───────────────────┘  └──────────────────┘  └──────────────────┘

Cùng "Product" nhưng mỗi context cần thông tin khác nhau!

2.2 Bounded Context

Bounded Context is the logical boundary within which a consistent domain model is applied.

Principles:

  • Each Bounded Context = 1 Microservice (or a small group)
  • Each Bounded Context = 1 Micro Frontend (or part of UI)
  • Between contexts: communicate via API or Events (no shared database!)

2.3 Context Mapping

Bounded Contexts do not exist independently — they have relationships with each other:

Context Map:
┌──────────────┐          ┌──────────────┐
│   Product    │◄─────────│    Order     │
│   Catalog    │ Upstream  │  Processing  │
│  (Supplier)  │──────────►│ (Consumer)   │
└──────────────┘          └──────┬───────┘
                                 │
                          ┌──────┴───────┐
                          │   Payment    │
                          │   Gateway    │
                          └──────────────┘

Relationship patterns:

PatternDescriptionWhen to use
Customer-SupplierUpstream provides, Downstream consumesExplicit dependency relationships
Shared KernelShare part of the modelThe two contexts are closely related
Anti-Corruption LayerLayer transition between two modelsLegacy system integration
Published LanguageStandard API/Schema for communicationMany consumers need to use

3. Tactical DDD — Design details

3.1 Aggregates

Aggregate is a group of entities treated as a unit of consistency. All changes go through Aggregate Root.

Order Aggregate:
┌─────────────────────────────────┐
│  Order (Aggregate Root)         │
│  ├── OrderItem                  │
│  ├── OrderItem                  │
│  └── ShippingAddress            │
│                                 │
│  Invariants:                    │
│  - Total = sum(items.price)     │
│  - Status transitions are valid │
│  - At least 1 item required     │
└─────────────────────────────────┘

3.2 Domain Events

Domain Events describes events that occurred in the domain:

// Domain Events
interface OrderPlaced {
  orderId: string;
  customerId: string;
  items: OrderItem[];
  totalAmount: number;
  placedAt: Date;
}

interface PaymentConfirmed {
  paymentId: string;
  orderId: string;
  amount: number;
  confirmedAt: Date;
}

// Event flow
OrderPlaced → PaymentService listens → PaymentConfirmed → ShippingService listens

4. Event Storming — Domain Discovery

4.1 What is Event Storming?

Event Storming is a workshop where developers + domain experts explore domains together by pasting sticky notes:

Timeline: ─────────────────────────────────────────────►

🟧 Domain Event     🟦 Command          🟨 Policy/Rule
"Order Placed"     "Place Order"       "If payment fails,
                                        cancel order"

🟪 Aggregate        🟩 External System   🔴 Hot Spot
"Order"            "Payment Gateway"   "Race condition
                                        when checking stock"

4.2 From Event Storming to Bounded Context

After the workshop, group related events → define Bounded Context → map to Microservices + Micro Frontends.

Event Storming Results → Bounded Contexts:
├── Product Context: ProductCreated, ProductUpdated, CategoryChanged
├── Cart Context: ItemAddedToCart, CartAbandoned, CouponApplied
├── Order Context: OrderPlaced, OrderConfirmed, OrderShipped
├── Payment Context: PaymentInitiated, PaymentConfirmed, RefundIssued
└── User Context: UserRegistered, ProfileUpdated, AddressAdded

Mapping:
├── Product Context → Product Microservice + Product MFE
├── Cart Context    → Cart Microservice + Cart MFE
├── Order Context   → Order Microservice + Checkout MFE
├── Payment Context → Payment Microservice (no separate MFE)
└── User Context    → User Microservice + Account MFE

5. Apply DDD to Micro Frontend

5.1 Bounded Context → Micro Frontend

Each Bounded Context not only maps into 1 Microservice but also maps into 1 Micro Frontend:

Bounded Context: Product Catalog
├── Backend: Product Microservice
│   ├── REST API / GraphQL
│   ├── PostgreSQL database
│   └── Search index (Elasticsearch)
│
├── Frontend: Product Micro Frontend
│   ├── Product listing page
│   ├── Product detail page
│   ├── Search & filter UI
│   └── Product reviews
│
└── Team: Product Team (full-stack ownership)

5.2 Team Topology according to DDD

┌──────────────────────────────────────────┐
│              Organization                │
├──────────┬──────────┬────────┬───────────┤
│ Team     │ Team     │ Team   │ Team      │
│ Product  │ Cart     │ Order  │ Platform  │
├──────────┼──────────┼────────┼───────────┤
│ MFE:     │ MFE:     │ MFE:   │ Shell App │
│ Product  │ Cart     │ Order  │ Design Sys│
│ pages    │ sidebar  │ pages  │ Auth      │
├──────────┼──────────┼────────┼───────────┤
│ µS:      │ µS:      │ µS:    │ API GW    │
│ Product  │ Cart     │ Order  │ Infra     │
│ API      │ API      │ API    │           │
├──────────┼──────────┼────────┼───────────┤
│ DB:      │ DB:      │ DB:    │ Shared    │
│ Postgres │ Redis    │ Postgres│ Services │
└──────────┴──────────┴────────┴───────────┘

Summary

  • DDD is the thinking foundation for properly decomposing systems
  • Bounded Context defines the boundary → maps to Microservices + Micro Frontends
  • Event Storming helps explore domains with business experts
  • Context Mapping defines the relationship between contexts
  • Dividing by domain (not by technical layer) is the golden rule

Next article: Lesson 3: Full-Stack overview architecture — Microservices + Micro Frontend + BFF