microservices-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Microservices Patterns

微服务模式

The craft of decomposing systems into independent services and making them work reliably together. Covers decomposition strategies, communication patterns, data ownership, and the resilience patterns that keep a distributed system from cascading into total failure.
将系统分解为独立服务并确保它们可靠协作的技巧。涵盖分解策略、通信模式、数据所有权,以及防止分布式系统彻底崩溃的弹性模式。

When to Use

适用场景

Use for:
  • Deciding whether to decompose a monolith and where to start
  • Designing service boundaries using bounded contexts and domain-driven design
  • Choosing between synchronous (REST/gRPC) and asynchronous (events) communication
  • Implementing saga pattern for distributed transactions
  • Designing API gateways and backend-for-frontend (BFF) layers
  • Applying circuit breaker, bulkhead, and retry patterns
  • Event sourcing and CQRS design
  • Service discovery and load balancing strategies
NOT for:
  • Monolith internal architecture (use
    database-design-patterns
    ,
    api-architect
    )
  • Serverless function design and deployment
  • Kubernetes infrastructure and deployment configuration (use
    terraform-iac-expert
    )
  • Service mesh configuration (Istio, Linkerd) — mention it, not the core focus
  • Single-service performance optimization (use
    performance-profiling
    )

适用场景:
  • 决定是否拆分单体架构以及从何处入手
  • 使用bounded context(限界上下文)和领域驱动设计定义服务边界
  • 在同步(REST/gRPC)和异步(事件)通信之间做选择
  • 实现saga模式处理分布式事务
  • 设计API网关和Backend-for-Frontend(BFF)层
  • 应用断路器、舱壁和重试模式
  • 事件溯源与CQRS设计
  • 服务发现与负载均衡策略
不适用场景:
  • 单体内部架构(使用
    database-design-patterns
    api-architect
  • 无服务器函数的设计与部署
  • Kubernetes基础设施与部署配置(使用
    terraform-iac-expert
  • Service Mesh配置(Istio、Linkerd)——仅提及,非核心关注点
  • 单服务性能优化(使用
    performance-profiling

Core Decision: Monolith vs Microservices vs Modular Monolith

核心决策:单体架构 vs 微服务 vs 模块化单体

mermaid
flowchart TD
    Start[New project or architecture review] --> TeamSize{Team size?}
    TeamSize -->|1-8 engineers| Small[Modular monolith first]
    TeamSize -->|9-25| Medium{Domain complexity?}
    TeamSize -->|25+| Large{Independent deploy needed?}

    Small --> S1[Build well-factored modules with clear boundaries]
    S1 --> S2{Growing pains?}
    S2 -->|No| S1
    S2 -->|Yes: deploy conflicts, team coupling| Extract[Extract services at natural seams]

    Medium -->|Simple, few domains| Modular[Modular monolith]
    Medium -->|Complex, many bounded contexts| MicroQ{Org structure?}
    MicroQ -->|Teams align to domains| Micro[Microservices]
    MicroQ -->|Teams are cross-functional| Modular

    Large -->|Yes, teams blocked waiting for each other| Micro
    Large -->|No, deploys are coordinated| Modular

    Micro --> Check{Check: do services deploy independently?}
    Check -->|No, they must release together| Problem[You have a distributed monolith]
    Check -->|Yes| Proceed[Proceed with microservices]
mermaid
flowchart TD
    Start[New project or architecture review] --> TeamSize{Team size?}
    TeamSize -->|1-8 engineers| Small[Modular monolith first]
    TeamSize -->|9-25| Medium{Domain complexity?}
    TeamSize -->|25+| Large{Independent deploy needed?}

    Small --> S1[Build well-factored modules with clear boundaries]
    S1 --> S2{Growing pains?}
    S2 -->|No| S1
    S2 -->|Yes: deploy conflicts, team coupling| Extract[Extract services at natural seams]

    Medium -->|Simple, few domains| Modular[Modular monolith]
    Medium -->|Complex, many bounded contexts| MicroQ{Org structure?}
    MicroQ -->|Teams align to domains| Micro[Microservices]
    MicroQ -->|Teams are cross-functional| Modular

    Large -->|Yes, teams blocked waiting for each other| Micro
    Large -->|No, deploys are coordinated| Modular

    Micro --> Check{Check: do services deploy independently?}
    Check -->|No, they must release together| Problem[You have a distributed monolith]
    Check -->|Yes| Proceed[Proceed with microservices]

When Microservices Are Worth the Cost

微服务值得投入成本的场景

The benefits of microservices: independent scaling, independent deployment, technology diversity, fault isolation. The cost: distributed systems complexity, eventual consistency, operational overhead, network latency.
Microservices make sense when:
  • Different parts of the system have dramatically different scaling requirements
  • Teams are large enough that a single codebase creates coordination overhead
  • Domains are stable enough that service boundaries will not change constantly
  • The team has the operational maturity to manage distributed systems (observability, deployment automation, on-call)
A startup with 4 engineers shipping features daily almost certainly should not be building microservices. A company with 200 engineers where the checkout team is blocked waiting for the catalog team to release — that is a microservices situation.

微服务的优势:独立伸缩、独立部署、技术多样性、故障隔离。成本:分布式系统复杂度、最终一致性、运维开销、网络延迟。
在以下场景中微服务是合理的:
  • 系统不同部分的伸缩需求差异极大
  • 团队规模足够大,单一代码库会导致协作开销
  • 领域足够稳定,服务边界不会频繁变化
  • 团队具备管理分布式系统的运维成熟度(可观测性、部署自动化、值班响应)
一个拥有4名工程师、每日发布功能的初创公司几乎肯定不应构建微服务。而一家拥有200名工程师,结账团队因等待目录团队发布而受阻的公司,则适合采用微服务。

Service Decomposition

服务分解

Bounded Context

Bounded Context(限界上下文)

A bounded context is the explicit boundary within which a domain model applies. Language, concepts, and rules inside the boundary are consistent. At boundaries, explicit translation happens.
Order Service:
  - "customer" = { id, shippingAddress, paymentMethod }
  - "product" = { id, price, quantity }

Catalog Service:
  - "product" = { id, name, description, images, attributes, category }
  - "customer" — not a concept here at all

Recommendation Service:
  - "customer" = { id, browsingHistory, purchaseHistory }
  - "product" = { id, category, tags }
The same word ("product") means different things in each context. This is correct — forcing a single shared model across all services creates tight coupling.
限界上下文是领域模型适用的明确边界。边界内的语言、概念和规则保持一致。在边界处需进行显式转换。
Order Service:
  - "customer" = { id, shippingAddress, paymentMethod }
  - "product" = { id, price, quantity }

Catalog Service:
  - "product" = { id, name, description, images, attributes, category }
  - "customer" — not a concept here at all

Recommendation Service:
  - "customer" = { id, browsingHistory, purchaseHistory }
  - "product" = { id, category, tags }
同一个词(“product”)在每个上下文中含义不同。这是正确的——强制所有服务使用单一共享模型会导致紧耦合。

Strangler Fig Pattern

Strangler Fig Pattern(绞杀者模式)

The safe way to decompose a monolith: route traffic through a facade, extract functionality piece by piece, never do a big-bang rewrite.
mermaid
flowchart LR
    Client --> Facade[API Gateway / Facade]
    Facade --> Monolith[(Monolith)]
    Facade --> NewService[New Service]

    subgraph "Phase 1: Identify seam"
        Monolith
    end
    subgraph "Phase 2: Route new traffic"
        NewService
    end
    subgraph "Phase 3: Migrate & delete"
        Monolith -->|Sunset| Deleted[Deleted]
    end
Steps:
  1. Identify a seam — a module in the monolith with clear inputs/outputs and minimal internal dependencies
  2. Stand up the new service — implement the same functionality independently
  3. Route new traffic to the new service; old traffic still goes to monolith
  4. Migrate old data and traffic gradually
  5. Delete the monolith code once confidence is high
Never try to extract the whole monolith at once. One seam at a time.

拆分单体架构的安全方式:通过 facade 路由流量,逐步提取功能,绝不进行大爆炸式重写。
mermaid
flowchart LR
    Client --> Facade[API Gateway / Facade]
    Facade --> Monolith[(Monolith)]
    Facade --> NewService[New Service]

    subgraph "Phase 1: Identify seam"
        Monolith
    end
    subgraph "Phase 2: Route new traffic"
        NewService
    end
    subgraph "Phase 3: Migrate & delete"
        Monolith -->|Sunset| Deleted[Deleted]
    end
步骤:
  1. 识别拆分点——单体中具有清晰输入/输出且内部依赖极少的模块
  2. 搭建新服务——独立实现相同功能
  3. 将新流量路由到新服务;旧流量仍流向单体
  4. 逐步迁移旧数据和流量
  5. 确认无误后删除单体代码
切勿尝试一次性提取整个单体。一次只处理一个拆分点。

Communication Patterns

通信模式

Synchronous vs Asynchronous

同步 vs 异步

mermaid
flowchart TD
    Decision[Choosing communication] --> Need{Does caller need an immediate response?}
    Need -->|Yes, and can fail if downstream is down| Sync[Synchronous: REST or gRPC]
    Need -->|No, or needs to tolerate downstream outages| Async[Asynchronous: events/messages]

    Sync --> SyncQ{Protocol?}
    SyncQ -->|CRUD operations, public APIs| REST[REST/HTTP]
    SyncQ -->|Internal, high throughput, streaming| GRPC[gRPC]

    Async --> AsyncQ{Pattern?}
    AsyncQ -->|Fire and forget, fan-out| Events[Event bus: Kafka, NATS, SNS]
    AsyncQ -->|Work queue, at-least-once delivery| Queue[Message queue: SQS, RabbitMQ]
    AsyncQ -->|Multi-step transaction coordination| Saga
mermaid
flowchart TD
    Decision[Choosing communication] --> Need{Does caller need an immediate response?}
    Need -->|Yes, and can fail if downstream is down| Sync[Synchronous: REST or gRPC]
    Need -->|No, or needs to tolerate downstream outages| Async[Asynchronous: events/messages]

    Sync --> SyncQ{Protocol?}
    SyncQ -->|CRUD operations, public APIs| REST[REST/HTTP]
    SyncQ -->|Internal, high throughput, streaming| GRPC[gRPC]

    Async --> AsyncQ{Pattern?}
    AsyncQ -->|Fire and forget, fan-out| Events[Event bus: Kafka, NATS, SNS]
    AsyncQ -->|Work queue, at-least-once delivery| Queue[Message queue: SQS, RabbitMQ]
    AsyncQ -->|Multi-step transaction coordination| Saga

Circuit Breaker

Circuit Breaker(断路器)

Prevents a slow/down downstream service from taking out the caller.
States:
  CLOSED (normal)    → requests pass through
  OPEN (tripped)     → requests fail fast without calling downstream
  HALF-OPEN (probe)  → one request allowed through to test recovery

Transitions:
  CLOSED  → OPEN:      failure threshold exceeded (e.g., 5 failures in 10 seconds)
  OPEN    → HALF-OPEN: after timeout (e.g., 30 seconds)
  HALF-OPEN → CLOSED:  probe request succeeds
  HALF-OPEN → OPEN:    probe request fails
js
// Example using opossum (Node.js circuit breaker library)
const CircuitBreaker = require('opossum');

const options = {
  timeout: 3000,           // If function takes longer than 3s, trigger failure
  errorThresholdPercentage: 50,  // Open circuit when 50% of requests fail
  resetTimeout: 30000,     // Try again after 30s
};

const breaker = new CircuitBreaker(callPaymentService, options);

breaker.on('open', () => console.log('Circuit open — payment service unreachable'));
breaker.on('halfOpen', () => console.log('Testing payment service recovery'));
breaker.on('close', () => console.log('Circuit closed — payment service recovered'));

// Fallback when circuit is open
breaker.fallback(() => ({ status: 'pending', message: 'Payment queued for retry' }));
防止缓慢/故障的下游服务拖垮调用方。
States:
  CLOSED (normal)    → requests pass through
  OPEN (tripped)     → requests fail fast without calling downstream
  HALF-OPEN (probe)  → one request allowed through to test recovery

Transitions:
  CLOSED  → OPEN:      failure threshold exceeded (e.g., 5 failures in 10 seconds)
  OPEN    → HALF-OPEN: after timeout (e.g., 30 seconds)
  HALF-OPEN → CLOSED:  probe request succeeds
  HALF-OPEN → OPEN:    probe request fails
js
// Example using opossum (Node.js circuit breaker library)
const CircuitBreaker = require('opossum');

const options = {
  timeout: 3000,           // If function takes longer than 3s, trigger failure
  errorThresholdPercentage: 50,  // Open circuit when 50% of requests fail
  resetTimeout: 30000,     // Try again after 30s
};

const breaker = new CircuitBreaker(callPaymentService, options);

breaker.on('open', () => console.log('Circuit open — payment service unreachable'));
breaker.on('halfOpen', () => console.log('Testing payment service recovery'));
breaker.on('close', () => console.log('Circuit closed — payment service recovered'));

// Fallback when circuit is open
breaker.fallback(() => ({ status: 'pending', message: 'Payment queued for retry' }));

Bulkhead

Bulkhead(舱壁)

Isolate failures: give each downstream service its own thread pool/connection pool so one slow service cannot exhaust all resources.
js
// Naive: single shared pool — one slow dependency starves everything
const pool = new DatabasePool({ max: 50 });

// Bulkhead: separate pools per service
const pools = {
  payments: new Pool({ max: 10 }),    // Max 10 concurrent payment calls
  catalog: new Pool({ max: 20 }),     // Catalog can use more
  notifications: new Pool({ max: 5 }), // Limit low-priority work
};

隔离故障:为每个下游服务分配独立的线程池/连接池,避免一个缓慢的服务耗尽所有资源。
js
// Naive: single shared pool — one slow dependency starves everything
const pool = new DatabasePool({ max: 50 });

// Bulkhead: separate pools per service
const pools = {
  payments: new Pool({ max: 10 }),    // Max 10 concurrent payment calls
  catalog: new Pool({ max: 20 }),     // Catalog can use more
  notifications: new Pool({ max: 5 }), // Limit low-priority work
};

Saga Pattern: Distributed Transactions

Saga Pattern: 分布式事务

Sagas replace distributed ACID transactions (which require 2-phase commit and are expensive) with a sequence of local transactions, each publishing events or messages to trigger the next step. If a step fails, compensating transactions undo previous steps.
Saga模式用一系列本地事务替代分布式ACID事务(需要两阶段提交,成本高昂),每个本地事务发布事件或消息触发下一步。如果某一步失败,补偿事务会撤销之前的步骤。

Orchestration Saga

Orchestration Saga(编排式Saga)

A central orchestrator (saga coordinator) tells each service what to do and handles failure by issuing compensating commands.
mermaid
sequenceDiagram
    participant O as Order Saga Orchestrator
    participant OS as Order Service
    participant IS as Inventory Service
    participant PS as Payment Service
    participant NS as Notification Service

    O->>OS: CreateOrder
    OS-->>O: OrderCreated

    O->>IS: ReserveInventory
    IS-->>O: InventoryReserved

    O->>PS: ProcessPayment
    alt Payment succeeds
        PS-->>O: PaymentProcessed
        O->>NS: SendConfirmation
        NS-->>O: NotificationSent
        O->>OS: MarkOrderComplete
    else Payment fails
        PS-->>O: PaymentFailed
        O->>IS: ReleaseInventory  [compensating transaction]
        O->>OS: CancelOrder       [compensating transaction]
    end
When to use orchestration: Complex workflows with many steps and conditional branching. The saga state and failure handling are explicit and centralized. Easier to observe (one place to look), but creates a central coordinator that knows too much.
中央编排器(Saga协调器)告知每个服务执行操作,并通过发出补偿命令处理故障。
mermaid
sequenceDiagram
    participant O as Order Saga Orchestrator
    participant OS as Order Service
    participant IS as Inventory Service
    participant PS as Payment Service
    participant NS as Notification Service

    O->>OS: CreateOrder
    OS-->>O: OrderCreated

    O->>IS: ReserveInventory
    IS-->>O: InventoryReserved

    O->>PS: ProcessPayment
    alt Payment succeeds
        PS-->>O: PaymentProcessed
        O->>NS: SendConfirmation
        NS-->>O: NotificationSent
        O->>OS: MarkOrderComplete
    else Payment fails
        PS-->>O: PaymentFailed
        O->>IS: ReleaseInventory  [compensating transaction]
        O->>OS: CancelOrder       [compensating transaction]
    end
何时使用编排式:包含多个步骤和条件分支的复杂工作流。Saga状态和故障处理显式且集中化。易于观测(只需查看一处),但会创建一个了解过多细节的中央协调器。

Choreography Saga

Choreography Saga(编排式Saga)

No central coordinator. Each service listens for events and decides what to do, then emits its own events.
mermaid
sequenceDiagram
    participant OS as Order Service
    participant IS as Inventory Service
    participant PS as Payment Service
    participant NS as Notification Service
    participant EB as Event Bus

    OS->>EB: OrderCreated
    EB->>IS: OrderCreated (consumed)
    IS->>EB: InventoryReserved
    EB->>PS: InventoryReserved (consumed)

    alt Payment succeeds
        PS->>EB: PaymentProcessed
        EB->>NS: PaymentProcessed (consumed)
        NS->>EB: NotificationSent
        EB->>OS: NotificationSent (consumed)
        OS->>OS: MarkOrderComplete
    else Payment fails
        PS->>EB: PaymentFailed
        EB->>IS: PaymentFailed (consumed)
        IS->>IS: ReleaseInventory [compensating]
        IS->>EB: InventoryReleased
        EB->>OS: InventoryReleased (consumed)
        OS->>OS: CancelOrder [compensating]
    end
When to use choreography: Simpler workflows with few steps. Services are more autonomous — no service knows the overall flow. Harder to observe (must trace across services), but more decoupled.

无中央协调器。每个服务监听事件并决定操作,然后发出自己的事件。
mermaid
sequenceDiagram
    participant OS as Order Service
    participant IS as Inventory Service
    participant PS as Payment Service
    participant NS as Notification Service
    participant EB as Event Bus

    OS->>EB: OrderCreated
    EB->>IS: OrderCreated (consumed)
    IS->>EB: InventoryReserved
    EB->>PS: InventoryReserved (consumed)

    alt Payment succeeds
        PS->>EB: PaymentProcessed
        EB->>NS: PaymentProcessed (consumed)
        NS->>EB: NotificationSent
        EB->>OS: NotificationSent (consumed)
        OS->>OS: MarkOrderComplete
    else Payment fails
        PS->>EB: PaymentFailed
        EB->>IS: PaymentFailed (consumed)
        IS->>IS: ReleaseInventory [compensating]
        IS->>EB: InventoryReleased
        EB->>OS: InventoryReleased (consumed)
        OS->>OS: CancelOrder [compensating]
    end
何时使用编排式:步骤较少的简单工作流。服务更自治——没有服务了解整体流程。观测难度更高(需跨服务追踪),但耦合度更低。

Anti-Pattern: Distributed Monolith

反模式:分布式单体

Novice: Splits the application into 8 services, but every release requires deploying all 8 simultaneously because they share a database schema or make synchronous calls that break if versions mismatch.
Expert: Microservices that must be deployed together are not microservices — they are a distributed monolith with all the downsides of both architectures and the benefits of neither. Real microservices deploy independently, tolerate version skew through backward-compatible APIs and event schemas, and own their data exclusively. If you cannot answer "can I deploy Service A without touching Service B?" with "yes," you have not finished the decomposition.
Detection: Your deploy runbook says "deploy these services in this order." Your integration tests fail when services run different versions. Teams coordinate release dates across service boundaries.

新手做法:将应用拆分为8个服务,但每次发布都需同时部署所有8个服务,因为它们共享数据库架构或进行同步调用,版本不匹配会导致崩溃。
专家做法:必须一起部署的“微服务”不是真正的微服务——它们是分布式单体,兼具两种架构的缺点,却无任何优势。真正的微服务可独立部署,通过向后兼容的API和事件 schema 容忍版本差异,并独占数据所有权。如果你无法肯定回答“我可以部署Service A而不触碰Service B吗?”,说明分解还未完成。
检测信号:部署手册要求“按顺序部署这些服务”。服务运行不同版本时集成测试失败。团队跨服务边界协调发布日期。

Anti-Pattern: Synchronous Call Chains

反模式:同步调用链

Novice: User request hits API Gateway → Order Service → calls Inventory Service → which calls Warehouse Service → which calls Shipping Service. All synchronous HTTP.
Expert: A chain of 4 synchronous calls multiplies latency and availability failure. If each service has 99.9% availability, a chain of 4 gives 99.6% availability — 3.5 hours of downtime per month. Latency compounds: 4 services at 50ms each = 200ms minimum, plus network overhead. Use asynchronous events for operations that do not need to block the user, and apply the circuit breaker pattern on every synchronous call. If a chain is longer than 2-3 hops, redesign the data ownership — the caller is probably missing data it should own.
Detection: Request waterfalls in distributed traces where service A is waiting for B, B is waiting for C. p99 latency much worse than p50 (cascading tail latency).

新手做法:用户请求到达API网关 → Order Service → 调用Inventory Service → 调用Warehouse Service → 调用Shipping Service。全部为同步HTTP调用。
专家做法:4个同步调用的链会放大延迟和可用性故障。如果每个服务的可用性为99.9%,4个服务组成的链可用性为99.6%——每月停机3.5小时。延迟叠加:4个服务各50ms,加上网络开销,最小延迟为200ms。对于无需阻塞用户的操作,使用异步事件;对每个同步调用应用断路器模式。如果调用链超过2-3跳,重新设计数据所有权——调用方可能缺少应归其所有的数据。
检测信号:分布式追踪中出现请求瀑布,Service A等待B,B等待C。p99延迟远差于p50(级联尾延迟)。

Anti-Pattern: Shared Database

反模式:共享数据库

Novice: Microservices share a PostgreSQL database to avoid the complexity of cross-service data access.
Expert: A shared database is tight coupling at the storage layer. Any schema change must be coordinated across all services that touch that table. One service's slow query can lock rows that another service needs. You cannot independently scale services with different data access patterns. Each service must own its data store — schema, indexes, and all. Cross-service data access goes through the owning service's API or via events. Yes, this means you cannot do a JOIN across service boundaries. That is the constraint that forces clean data ownership.
Detection: Service A's tests fail because Service B modified a shared table schema. Services are using the same database connection credentials. Schema migrations require downtime for multiple services simultaneously.

新手做法:微服务共享PostgreSQL数据库,以避免跨服务数据访问的复杂性。
专家做法:共享数据库是存储层的紧耦合。任何 schema 变更都需协调所有触及该表的服务。一个服务的慢查询会锁定另一个服务需要的行。你无法针对不同数据访问模式独立伸缩服务。每个服务必须独占其数据存储——包括schema、索引等。跨服务数据访问需通过所属服务的API或事件实现。是的,这意味着你无法跨服务边界执行JOIN。这正是强制实现清晰数据所有权的约束。
检测信号:Service B修改共享表schema导致Service A的测试失败。服务使用相同的数据库连接凭证。schema迁移需多个服务同时停机。

CQRS and Event Sourcing

CQRS与事件溯源

CQRS (Command Query Responsibility Segregation)

CQRS(命令查询职责分离)

Separate the write model (commands, enforces invariants) from the read model (queries, optimized for display).
Write side:                          Read side:
  POST /orders → OrderService          GET /orders/{id} → OrderQueryService
  Validates business rules             Materialized view, denormalized
  Writes to order aggregate            Updated from events
  Emits OrderPlaced event              No business logic, just data
CQRS is valuable when read patterns are radically different from write patterns — e.g., write validates complex business rules but reads need denormalized views spanning multiple aggregates.
将写入模型(命令,强制执行不变量)与读取模型(查询,针对显示优化)分离。
Write side:                          Read side:
  POST /orders → OrderService          GET /orders/{id} → OrderQueryService
  Validates business rules             Materialized view, denormalized
  Writes to order aggregate            Updated from events
  Emits OrderPlaced event              No business logic, just data
当读取模式与写入模式差异极大时,CQRS很有价值——例如,写入需验证复杂业务规则,而读取需要跨多个聚合的非规范化视图。

Event Sourcing

Event Sourcing(事件溯源)

Instead of storing current state, store the sequence of events that produced that state. Current state is derived by replaying events.
js
// Traditional: store current state
await db.update('orders', { id, status: 'SHIPPED', shippedAt: new Date() });

// Event sourcing: store what happened
await eventStore.append('order-' + id, {
  type: 'OrderShipped',
  payload: { orderId: id, carrier: 'FedEx', trackingNumber: '9400...' },
  timestamp: new Date(),
  version: 4,  // optimistic concurrency control
});

// Derive current state by replaying
async function getOrderState(orderId) {
  const events = await eventStore.getEvents('order-' + orderId);
  return events.reduce(applyEvent, { status: null, items: [], history: [] });
}
Event sourcing provides a complete audit log, time travel (replay to any point), and the ability to derive new read models from historical events. The tradeoff: querying is harder (must use projections), and event schema evolution requires careful versioning.

不存储当前状态,而是存储产生该状态的事件序列。通过重放事件推导当前状态。
js
// Traditional: store current state
await db.update('orders', { id, status: 'SHIPPED', shippedAt: new Date() });

// Event sourcing: store what happened
await eventStore.append('order-' + id, {
  type: 'OrderShipped',
  payload: { orderId: id, carrier: 'FedEx', trackingNumber: '9400...' },
  timestamp: new Date(),
  version: 4,  // optimistic concurrency control
});

// Derive current state by replaying
async function getOrderState(orderId) {
  const events = await eventStore.getEvents('order-' + orderId);
  return events.reduce(applyEvent, { status: null, items: [], history: [] });
}
事件溯源提供完整的审计日志、时间回溯(重放到任意时间点),以及从历史事件推导新读取模型的能力。权衡:查询难度更高(必须使用投影),事件schema演进需谨慎版本控制。

API Gateway and BFF

API网关与BFF

API Gateway Responsibilities

API网关职责

  • Authentication/authorization: validate JWT, check scopes before forwarding
  • Rate limiting: per-client, per-endpoint limits
  • Request routing: route to appropriate service based on path/header
  • Protocol translation: external REST to internal gRPC, or vice versa
  • Response aggregation: fan out to multiple services, merge results
  • SSL termination: handle HTTPS externally, HTTP internally
  • 认证/授权:转发前验证JWT、检查权限范围
  • 限流:按客户端、按端点设置限制
  • 请求路由:根据路径/头信息路由到对应服务
  • 协议转换:外部REST转内部gRPC,或反之
  • 响应聚合:扇出到多个服务,合并结果
  • SSL终止:外部处理HTTPS,内部使用HTTP

Backend for Frontend (BFF)

Backend for Frontend(BFF)

One generic API gateway becomes a problem: mobile clients need small payloads, web clients need rich data, and the gateway is making tradeoffs for everyone. BFF creates a separate gateway per client type.
Mobile App → BFF-Mobile → [User Service, Order Service]
                           (small payloads, battery-conscious)

Web App → BFF-Web → [User Service, Order Service, Recommendation Service]
                     (rich data, aggregated views)

Third-party API → Public API Gateway → (rate-limited, versioned, documented)

单一通用API网关会出现问题:移动客户端需要小负载,Web客户端需要丰富数据,网关需为各方做权衡。BFF为每种客户端类型创建独立网关。
Mobile App → BFF-Mobile → [User Service, Order Service]
                           (small payloads, battery-conscious)

Web App → BFF-Web → [User Service, Order Service, Recommendation Service]
                     (rich data, aggregated views)

Third-party API → Public API Gateway → (rate-limited, versioned, documented)

Service Discovery

服务发现

Client-Side Discovery

Client-Side Discovery(客户端发现)

Clients query a service registry (Consul, Eureka) and load balance themselves.
js
// Client-side: ask registry, then call directly
const instances = await consul.health.service({ service: 'payment-service', passing: true });
const instance = loadBalance(instances);
const url = `http://${instance.Service.Address}:${instance.Service.Port}`;
await fetch(`${url}/api/charge`);
客户端查询服务注册中心(Consul、Eureka)并自行实现负载均衡。
js
// Client-side: ask registry, then call directly
const instances = await consul.health.service({ service: 'payment-service', passing: true });
const instance = loadBalance(instances);
const url = `http://${instance.Service.Address}:${instance.Service.Port}`;
await fetch(`${url}/api/charge`);

Server-Side Discovery

Server-Side Discovery(服务端发现)

Load balancer (nginx, AWS ALB, Kubernetes Service) handles discovery. Clients call the load balancer, which routes to healthy instances. This is simpler for clients — use it in Kubernetes (Kubernetes Services do this for you).

负载均衡器(nginx、AWS ALB、Kubernetes Service)处理发现。客户端调用负载均衡器,由其路由到健康实例。这对客户端更简单——在Kubernetes中可直接使用(Kubernetes Service已实现此功能)。

Saga Orchestration: State Machine Design

Saga编排:状态机设计

When to build your own vs use a framework

自行构建 vs 使用框架

Use Temporal.io or AWS Step Functions for production sagas. They solve durable execution, crash recovery, and visibility for you. Building your own saga engine is justified only when: (a) you need <10ms step latency that Temporal's persistence overhead doesn't allow, or (b) your org can't adopt another infrastructure dependency.
If the user is building their own, guide them with this design:
生产环境Saga使用Temporal.io或AWS Step Functions。它们为你解决持久化执行、崩溃恢复和可观测性问题。只有在以下情况才需自行构建Saga引擎:(a) 需要Temporal持久化开销无法满足的<10ms步骤延迟,或(b) 组织无法引入新的基础设施依赖。
如果用户选择自行构建,可遵循以下设计指南:

State Model

状态模型

Every saga persists a state record after each step transition. This is the recovery mechanism — if the coordinator crashes, it reads the last persisted state and resumes.
typescript
interface SagaState {
  sagaId: string;
  status: 'running' | 'compensating' | 'completed' | 'failed';
  currentStepIndex: number;
  completedSteps: string[];         // Step names that succeeded
  compensatedSteps: string[];       // Step names that were compensated
  context: Record<string, unknown>; // Accumulates results from each step
  startedAt: string;                // ISO timestamp
  updatedAt: string;
  failureReason?: string;
}
Persist this to a database (Postgres JSONB column is fine) — not Redis, not in-memory. If the coordinator crashes, the state must survive.
每个Saga在每次步骤转换后持久化状态记录。这是恢复机制——如果协调器崩溃,它会读取最后持久化的状态并继续执行。
typescript
interface SagaState {
  sagaId: string;
  status: 'running' | 'compensating' | 'completed' | 'failed';
  currentStepIndex: number;
  completedSteps: string[];         // Step names that succeeded
  compensatedSteps: string[];       // Step names that were compensated
  context: Record<string, unknown>; // Accumulates results from each step
  startedAt: string;                // ISO timestamp
  updatedAt: string;
  failureReason?: string;
}
将状态持久化到数据库(Postgres JSONB列即可)——不要用Redis,不要内存存储。如果协调器崩溃,状态必须保留。

Execution Rules

执行规则

  1. Forward execution: Run steps in order. After each step succeeds, persist state with
    currentStepIndex++
    and the step's result merged into
    context
    . The persist-then-advance ordering matters — if the process crashes between step execution and persist, the step re-runs on recovery. Steps MUST be idempotent.
  2. Failure triggers compensation: When a step fails, set
    status: 'compensating'
    and run compensations in reverse order starting from the last completed step. Only compensate steps that are in
    completedSteps
    and not already in
    compensatedSteps
    .
  3. Compensation is also persisted: After each compensation succeeds, add the step name to
    compensatedSteps
    and persist. If the coordinator crashes during compensation, it resumes compensating from where it left off — never double-compensating a step.
  4. Per-step timeouts: Each step declares its own timeout. A payment capture might need 30s; an email notification needs 5s. Wrap step execution in
    Promise.race([step.command(context), timeout(step.timeoutMs)])
    . On timeout, treat as failure and begin compensation.
  5. Resume logic: On coordinator startup, query for sagas with
    status: 'running'
    or
    status: 'compensating'
    . For
    running
    , re-execute the step at
    currentStepIndex
    (idempotent, so safe). For
    compensating
    , continue compensating from the first un-compensated step (reverse order).
  1. 正向执行:按顺序运行步骤。每个步骤成功后,持久化状态,
    currentStepIndex++
    并将步骤结果合并到
    context
    。先持久化再推进的顺序很重要——如果进程在步骤执行和持久化之间崩溃,恢复时会重新运行该步骤。步骤必须是幂等的。
  2. 失败触发补偿:当步骤失败时,设置
    status: 'compensating'
    ,并从最后完成的步骤开始反向运行补偿操作。仅补偿
    completedSteps
    中存在且未在
    compensatedSteps
    中的步骤。
  3. 补偿操作也需持久化:每个补偿操作成功后,将步骤名称添加到
    compensatedSteps
    并持久化。如果协调器在补偿过程中崩溃,恢复时会从第一个未补偿的步骤继续(反向顺序)——绝不重复补偿同一步骤。
  4. 按步骤设置超时:每个步骤声明自己的超时时间。支付捕获可能需要30s;邮件通知需要5s。将步骤执行包装在
    Promise.race([step.command(context), timeout(step.timeoutMs)])
    中。超时视为失败并开始补偿。
  5. 恢复逻辑:协调器启动时,查询
    status: 'running'
    status: 'compensating'
    的Saga。对于
    running
    状态,重新执行
    currentStepIndex
    对应的步骤(幂等,因此安全)。对于
    compensating
    状态,从第一个未补偿的步骤继续反向补偿。

Example: Order Saga

示例:订单Saga

Steps (forward):
  1. reserveInventory  → compensate: releaseInventory
  2. chargePayment     → compensate: refundPayment
  3. shipOrder         → compensate: cancelShipment
  4. sendConfirmation  → compensate: sendCancellationEmail

If chargePayment fails:
  → compensate releaseInventory (only step 1 completed)
  → set status: 'failed', record failureReason

If shipOrder fails:
  → compensate refundPayment (step 2)
  → compensate releaseInventory (step 1)
  → reverse order matters: refund before releasing inventory
Steps (forward):
  1. reserveInventory  → compensate: releaseInventory
  2. chargePayment     → compensate: refundPayment
  3. shipOrder         → compensate: cancelShipment
  4. sendConfirmation  → compensate: sendCancellationEmail

If chargePayment fails:
  → compensate releaseInventory (only step 1 completed)
  → set status: 'failed', record failureReason

If shipOrder fails:
  → compensate refundPayment (step 2)
  → compensate releaseInventory (step 1)
  → reverse order matters: refund before releasing inventory

What NOT to do

禁忌做法

  • Don't use an in-memory state machine — it dies with the process and your saga is stuck half-executed with no way to recover.
  • Don't compensate in forward order — if you refund after releasing inventory, another customer may have claimed that inventory. Reverse order preserves logical consistency.
  • Don't retry indefinitely — compensations can also fail. After 3 compensation retries, raise an alert for human intervention. A saga stuck in
    compensating
    for hours is worse than one that fails loudly.
  • Don't skip idempotency — the entire crash-recovery mechanism depends on it.
    reserveInventory
    called twice with the same saga ID must produce the same result, not double-reserve.

  • 不要使用内存状态机——进程死亡后Saga会卡在半执行状态,无法恢复。
  • 不要正向顺序补偿——如果先释放库存再退款,其他客户可能已占用该库存。反向顺序可保持逻辑一致性。
  • 不要无限重试——补偿操作也可能失败。3次补偿重试后,触发告警人工干预。长时间卡在
    compensating
    状态的Saga比直接失败更糟糕。
  • 不要跳过幂等性——整个崩溃恢复机制依赖于此。使用相同Saga ID调用两次
    reserveInventory
    必须产生相同结果,不能重复预留。

References

参考资料

  • references/communication-patterns.md
    — Consult for: REST vs gRPC decision matrix, async messaging brokers (Kafka vs RabbitMQ vs SQS), idempotency patterns, saga choreography vs orchestration tradeoffs, CQRS read model patterns
  • references/decomposition-strategies.md
    — Consult for: bounded context identification, strangler fig implementation steps, domain-driven decomposition techniques, team topology alignment, database decomposition patterns, data migration strategies
  • references/communication-patterns.md
    — 参考内容:REST vs gRPC决策矩阵、异步消息代理(Kafka vs RabbitMQ vs SQS)、幂等性模式、Saga编排 vs 编舞权衡、CQRS读取模型模式
  • references/decomposition-strategies.md
    — 参考内容:限界上下文识别、绞杀者模式实现步骤、领域驱动分解技巧、团队拓扑对齐、数据库分解模式、数据迁移策略