# Build Backend Service Patterns

> 服务架构模式——分层、通信、韧性。当需要设计后端服务架构、跨服务通信或处理分布式问题，或提到"微服务""重试""熔断"

- Skill: `zeroz-lab/build-backend-service-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zeroz-lab/build-backend-service-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zeroz-lab/build-backend-service-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: zeroz-lab (https://skillmd.com/u/zeroz-lab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zeroz-lab/build-backend-service-patterns

---


# Service Patterns — 服务架构模式


## 入口/出口
- **入口**: 涉及多个服务/模块的边界设计、通信模式选择、韧性策略
- **出口**: 模式选择记录 + 接口定义 + 韧性配置
- **指向**: 架构确定后进入 `build-backend-api-design`（定义具体接口）
- **输出路径**: `verify-workflow-review`（下游验证技能）
- **前置加载**: CANON.md

## 何时不使用
- 只是在单服务内部实现一个清晰函数或端点
- 服务边界、通信方式和容错策略已经由 spec/ADR 明确
- 问题集中在具体 API 合约或数据库 schema，而不是服务架构

## 服务分层

```
Presentation  →  API Handler / Controller（HTTP、验证、序列化）
     │
Business      →  Service（领域逻辑、规则、工作流）
     │
Data          →  Repository / Adapter（数据访问、外部 API 封装）
     │
Infrastructure →  DB、Cache、Queue、外部服务
```

**依赖方向:** 上层依赖下层，依层不跨层。Business 不直接 import HTTP 框架类型。

## 通信模式

| 模式 | 适用场景 | 示例 |
|------|---------|------|
| **同步 Request-Response** | 需要立即结果、强一致性 | 创建订单 → 返回订单号 |
| **异步 Message Queue** | 可延迟、高吞吐 | 订单创建后发送邮件通知 |
| **Event-Driven Pub/Sub** | 多消费者、解耦 | 订单完成 → (通知服务 + 分析服务 + 积分服务) |
| **Saga (编排)** | 跨服务长事务 | 下单 → 扣库存 → 扣款 → 通知（任一步失败 = 补偿回滚） |

**选择原则:**
```
需要立即响应 + 强一致？→ 同步
可延迟 + 需要韧性？  → 异步
多消费者 + 解耦？   → Event-Driven
跨服务长事务？      → Saga
```

## 韧性模式

### 重试与退避

```typescript
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxRetries) throw err;
      const delay = Math.pow(2, attempt) * 100;  // 100ms → 200ms → 400ms
      await sleep(delay);
    }
  }
  throw new Error('unreachable');
}
```

### 断路器

```
状态机:
  CLOSED → (连续失败 N 次) → OPEN（立即失败，不调用）
  OPEN → (等待 timeout) → HALF_OPEN（尝试一次）
  HALF_OPEN → 成功 → CLOSED | 失败 → OPEN
```

**断路器保护下游服务不被雪崩淹没。** 当调用已经反复失败时，快速失败 > 继续重试拖死上游。

### 幂等性

```typescript
// 每个写操作需要幂等键
interface CreateOrderRequest {
  idempotencyKey: string;  // 客户端生成 UUID
  items: OrderItem[];
}

// 服务端: 相同幂等键 → 返回相同结果（不重复创建）
async function createOrder(req: CreateOrderRequest): Promise<Order> {
  const existing = await db.orders.findBy({ idempotencyKey: req.idempotencyKey });
  if (existing) return existing;
  return db.orders.create(req);
}
```

**任何涉及支付的端点必须有幂等键。** 网络重试 + 重复扣款 = 灾难。

## 模式选择

| 需求 | 模式 | 复杂度代价 |
|------|------|-----------|
| CRUD 围绕单一实体 | Repository + Service | 低 |
| 读/写负载不对称 | CQRS（读写分离） | 中 |
| 跨服务事务 | Saga | 高 |
| 复杂业务规则链 | Chain of Responsibility / Pipeline | 中 |
| 多策略可切换 | Strategy Pattern | 低 |
| 领域逻辑密集 | Domain-Driven（Entity + Value Object + Aggregate） | 中-高 |

**原则:** 从最简单的模式开始。当代码变复杂**因为模式不够**时才升级，不因为"如此"。

## 错误处理

```typescript
// 区分可恢复错误 vs 不可恢复错误
class RetryableError extends Error { /* 网络超时、503 */ }
class FatalError extends Error { /* 验证失败、404 */ }

async function handleServiceCall(fn: () => Promise<T>): Promise<T> {
  try {
    return await fn();
  } catch (err) {
    if (err instanceof RetryableError) {
      return withRetry(fn);
    }
    throw err;  // Fatal — 上层处理
  }
}
```

## 常见说辞

| 说辞 | 现实 | 后果 |
|------|------|------|
| "以后需要时再加重试" | 网络是不确定的。从第一条跨服务调用开始就加重试。 | 无重试 → 单次网络抖动导致请求失败率从 0.1% 飙升到 5-10%，生产故障排查时间 2-8 小时 |
| "断路器太复杂，直接重试" | 一直重试失败的服务 = 拖着上游一起死。断路器是保护装置。 | 无断路器 → 下游宕机时上游持续重试耗尽线程池，级联故障扩散到 3-5 个服务 |
| "幂等性以后补" | 有支付的系统从第一天就需要。补幂等性需要改 API 合约。 | 缺幂等键 → 网络重试导致重复扣款/重复创建，单次事故赔偿成本 > 开发成本 10 倍 |
| "同步耦合没关系，微服务间调用很快" | 现在快。调用链 lengthens，故障面扩大。能不耦合就不耦合。 | 同步链 > 3 层 → 平均故障恢复时间翻倍，任何一层超时导致整条链失败 |
| "选 CQRS，虽然简单但万一以后要读扩展" | 为"万一"增加复杂度 = 过早工程化。简单需求简单方案。 | 过早 CQRS → 读写模型同步延迟 bug、运维复杂度提升 3-5 倍，90% 项目不需要 |

## 验证失败处理

| 失败场景 | 处理方式 |
|---------|---------|
| 跨服务 HTTP 调用无重试和超时 | 停止，为每个跨服务调用添加 withRetry + timeout 配置后再继续 |
| 支付相关端点缺少幂等键 | 停止，添加 idempotencyKey 字段到 API 合约，服务端实现幂等检查后再继续 |
| 队列消费者无死信队列 | 添加 DLQ 配置，失败消息进入 DLQ 而非反复消费，避免队列堵塞 |
| 同步调用链 > 3 层 | 重构为异步解耦或引入 Saga 编排，不允许串行穿透超过 3 个服务 |
| 服务边界按技术层而非业务领域划分 | 重画服务边界图，按业务领域（如订单、支付、用户）重新划分 |

## 好坏示例

### ✅ Good: 最简模式 + 韧性完整

```
订单创建流程:
- API Handler → 调用 OrderService.createOrder()
- OrderService → 写入 DB + 发事件到 Queue（异步解耦）
- 邮件服务消费 Queue 事件（异步，不阻塞订单创建）

韧性配置:
- OrderService → DB: withRetry(3, exponentialBackoff)
- OrderService → Queue: 断路器（5 次连续失败打开）
- createOrder API: idempotencyKey 必填
- Queue 消费者: 死信队列（失败 3 次进入 DLQ）

模式选择: Repository + Service（最简，匹配 CRUD 围绕单一实体需求）
```

### ❌ Bad: 过早工程化 + 缺韧性

```
订单创建流程:
- API → OrderService → InventoryService → PaymentService → NotificationService
  （同步链 4 层，任何一层超时整条链失败）

韧性配置: (未配置重试、断路器、幂等键)

模式选择: CQRS + Saga + Event Sourcing
  （当前只需 CRUD，过早引入 3 个高复杂度模式）
```

## 红旗 — STOP

- 单体拆成微服务但没有定义故障模式（网络分区、超时、下游宕机）
- 没有重试/断路器的跨服务 HTTP 调用
- 支付相关端点缺少幂等键
- 队列消费者没有死信队列（DLQ）—— 坏消息反复消费
- 同步调用链 > 3 层（请求串行穿透多个服务）
- 从表现层直接调用数据层（跳过 Business/Service）

## 输出模板

服务架构设计完成后，应在 ADR 或 `03-plan.md` 中记录以下结构：

```markdown
## Service Architecture — [功能名称]

### 服务边界
| 服务 | 业务领域 | 负责范围 |
|------|---------|---------|
| [服务 A] | [领域] | [职责描述] |
| [服务 B] | [领域] | [职责描述] |

### 通信模式
| 调用路径 | 模式 | 理由 |
|---------|------|------|
| [A → B] | 同步 / 异步 / Event / Saga | [需要立即结果 / 可延迟 / 多消费者 / 跨服务长事务] |

### 韧性配置
| 调用路径 | 重试策略 | 断路器 | 幂等性 | 超时 |
|---------|---------|--------|-------|------|
| [A → B] | [次数 + 退避] | [阈值] | [是否需要] | [毫秒值] |

| 队列 | 消费者 | 死信队列 | 重试次数 |
|------|--------|---------|---------|
| [Queue A] | [消费者] | [DLQ 配置] | [次数] |

### 模式选择
| 需求 | 选定模式 | 复杂度代价 | 选择理由 |
|------|---------|-----------|---------|
| [需求描述] | [模式名] | [低/中/高] | [当前需求最简方案] |

### 故障模式
| 单点 | 降级方案 | 影响范围 |
|------|---------|---------|
| [单点描述] | [降级策略] | [受影响服务/用户] |
```

## 验证清单

- [ ] 服务边界清晰（按业务领域，不按技术层）
- [ ] 跨服务调用有重试和超时配置
- [ ] 支付/扣款相关有幂等键
- [ ] 异步消费者有死信队列
- [ ] 故障模式已识别（单点在哪？降级方案？）
- [ ] 选择的是当前需要的最简模式

