架构风格:单体、SOA 与微服务 选了「单体还是微服务」;本章回答边界画在哪、对外说什么语言。没有清晰的 限界上下文 与 OpenAPI 契约,拆服务只会得到多个互相渗透的「小泥球」——集成靠口头约定,改接口即全站停摆。
领域驱动设计(DDD)战略设计关注「大问题如何切」:识别子域(Subdomain)类型,并为每个核心能力划定限界上下文(Bounded Context)。
| 类型 | 含义 | ShopFlow 示例 |
|---|---|---|
| 核心域 | 差异化竞争力,值得自研投入 | 订单编排、库存防超卖策略 |
| 支撑域 | 必需但非差异化,可适度定制 | 支付对接、物流通知 |
| 通用域 | 行业通用能力,优先买/用 SaaS | 短信验证码、对象存储 |
OrderLine 与 Inventory 里的 StockReservation 不必强行合并。flowchart LR
subgraph orderCtx [Order 上下文]
O[Order 聚合]
OL[OrderLine]
end
subgraph invCtx [Inventory 上下文]
S[Sku / Stock]
R[Reservation]
end
subgraph payCtx [Payment 上下文]
P[Payment]
Ref[Refund]
end
orderCtx -->|ReserveCommand API| invCtx
orderCtx -->|InitiatePayment API| payCtx
payCtx -->|PaymentCompleted 事件| orderCtx
箭头表示集成关系(API 或事件),不是共享数据库。各上下文内部模型互不直接引用。
Warehouse 字段skuId、qty 等集成所需最小字段GET /skus/{id} 按需查询架构风格:单体、SOA 与微服务 已对比分层单体与模块化单体;本节从 DDD 视角强调:微服务边界应沿业务能力(限界上下文)切,而非沿 Controller / Service / Repository 等技术层切。
| 维度 | 按技术层拆分 | 按业务能力 / 限界上下文拆分 |
|---|---|---|
| 包/服务结构 | user-controller、order-controller 各一服务,共享同一 DB |
order-service、inventory-service,各管各的数据 |
| 变更影响 | 改订单逻辑仍可能动「公共 order 表」 | 订单模型变更局限在 Order 上下文内 |
| 团队对齐 | 按「前端组 / 后端组」分工,与业务脱节 | 订单小队拥有 Order 上下文全栈 |
| 分布式代价 | 背上网络调用却无独立数据边界 → 分布式单体 | 真正独立部署与演进,但须设计 API/事件与一致性 |
// 反例:按「层」拆服务,却共享 order 表与实体
// order-query-service 直接读 order 表 join inventory 表
@Query("SELECT o, i FROM Order o JOIN Inventory i ON ...")
List<OrderDetailDto> findAllWithStock();
// 推荐:Order 上下文通过契约调用 Inventory
public OrderView getOrder(UUID orderId) {
Order order = orderRepo.findById(orderId);
StockSnapshot stock = inventoryClient.getSku(order.getSkuId());
return OrderView.from(order, stock); // 组合视图,非共享实体
}
模块/服务内仍可分层(controller → application → domain → infra);拆分维度是业务能力,不是禁止分层。
限界上下文对外通常以 RESTful HTTP API 暴露能力(practice 阶段亦用 OpenFeign 消费)。好的资源建模让调用方「读 URI 即懂语义」,并预留演进空间。
/orders、/skus/{skuId}/reservations,避免 /createOrder 动词路径。POST /orders/{id}/cancel 或 PATCH /orders/{id} + {"status":"CANCELLED"}。skuId,不在 URI 里嵌 inventory 内部仓库 ID,除非该 ID 已是稳定集成标识。POST /reservations)要求 Header Idempotency-Key 或 body 内 requestId,应对 部分失败 重试。| 上下文 | 方法 | 路径 | 说明 |
|---|---|---|---|
| Order | POST | /api/v1/orders | 创建订单 |
| Order | GET | /api/v1/orders/{orderId} | 查询订单 |
| Inventory | POST | /api/v1/reservations | 预留库存(幂等) |
| Inventory | GET | /api/v1/skus/{skuId} | 查询 SKU 可售量 |
| Payment | POST | /api/v1/payments | 发起支付 |
/api/v1/orders → /api/v2/orders:直观,网关路由简单/api/v1Accept: application/vnd.shopflow.order.v2+jsonApi-Version: 2{
"code": "INVENTORY_INSUFFICIENT",
"message": "SKU-1001 可售库存不足",
"traceId": "7f3a2b1c-...",
"details": { "skuId": "SKU-1001", "requested": 5, "available": 2 }
}
ORDER_ / INV_,网关可映射为统一表)。code 表达业务层语义。契约优先(Contract First):先写 OpenAPI 规范,再生成/实现 Controller 与客户端桩代码。这样 Order 与 Inventory 团队在编码前就对齐字段、错误码与幂等语义。
openapi: 3.1.0
info:
title: ShopFlow Inventory API
version: 1.0.0
paths:
/api/v1/reservations:
post:
operationId: createReservation
parameters:
- name: Idempotency-Key
in: header
required: true
schema: { type: string, format: uuid }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [orderId, skuId, quantity]
properties:
orderId: { type: string, format: uuid }
skuId: { type: string }
quantity: { type: integer, minimum: 1 }
responses:
'201':
description: 预留成功
'409':
description: 库存不足或冲突
openapi/inventory-api.yaml。在 微服务 集成中,调用方(消费者) 定义「我需要哪些字段与状态码」,写入契约文件;提供方 在 CI 中验证能否满足。这样 inventory 重构内部表结构时,只要仍满足 order-service 的契约,集成测试即可通过。
sequenceDiagram participant OC as order-service 消费者 participant Pact as 契约仓库 / CI participant IS as inventory-service 提供者 OC->>Pact: 提交期望契约(reserve 201/409) IS->>Pact: Provider 验证任务 Pact->>IS: 验证通过 / 失败 Note over OC,IS: 实现前对齐;改 API 须双方评审
对比「联调当天才发现字段少了」:契约将集成失败左移到 PR 阶段。
团队应显式选定真源:契约优先则以 YAML 为准;代码优先则须 CI 反向生成/校验 OpenAPI diff。ShopFlow 教程采用契约优先,与 SpringDoc 可并存(由 YAML 生成或双向校验)。
将本章方法落到 ShopFlow 全课主线:三个上下文各司其职,通过 API 网关 对外统一入口,内部走同步 API + 异步事件。
| 上下文 | 核心聚合 | 拥有的数据 | 对外能力 |
|---|---|---|---|
| Order | Order | 订单头、明细、状态机 | 创建/查询/取消订单;订阅 PaymentCompleted |
| Inventory | Sku、Reservation | 可售量、预留记录 | 预留/释放/扣减;强一致 防超卖 |
| Payment | Payment | 支付单、渠道回调 | 发起支付、退款;发布 PaymentCompleted |
sequenceDiagram participant U as 用户 participant GW as API Gateway participant O as order-service participant I as inventory-service participant P as payment-service U->>GW: POST /api/v1/orders GW->>O: 转发 O->>I: POST /reservations (Idempotency-Key) I-->>O: 201 Reserved O->>P: POST /payments P-->>O: 202 Accepted O-->>U: 201 Order PENDING_PAYMENT P-->>O: PaymentCompleted 事件 O->>O: 状态 → PAID
Order 编排流程;Inventory 与 Payment 不直接互调。失败补偿在 分布式数据:事务、幂等与 Saga(Saga)展开。
InventoryClient 将外部 DTO 转为域内 ReservationResult,隔离外部模型变化。不应:违反上下文数据所有权。应通过 API 或事件让 Order 上下文自行变更状态;跨库写是 分布式单体 典型信号。事件可靠投递可结合 Outbox(CAP、BASE 与一致性模型 预览,Kafka 事件驱动与 Redis 缓存 展开)。
下一章 通信模式:同步、异步与事件驱动 将把同步/异步、编排与编舞、Outbox 与本章契约映射到 Spring Cloud 概念。
每条对应上一节 h3,用于评审前 30 秒复述。
过关清单(勾选会保存在本浏览器)
0 / 5 已勾选
| 概念 | 一句 |
|---|---|
| 限界上下文 | 独立语言与模型;对外只暴露 API/事件 |
| 按能力拆分 | Order/Inventory/Payment,非按 Controller 拆 |
| 契约优先 | OpenAPI 先评审再编码 |
| CDC | 消费者定义期望,提供者 CI 验证 |
| 幂等键 | 写操作 Header Idempotency-Key |
下一步:勾选过关清单、完成测验与 Demo;下一章 通信模式:同步、异步与事件驱动 讲同步/异步通信与 Outbox 预览。
产出 Inventory 最小 OpenAPI 与三上下文映射图,对照错误码与幂等头约定自检。
POST /api/v1/reservations 补全 409 库存不足与幂等冲突的 JSON body(含 code、message)。Idempotency-Key header 参数,与正文 OpenAPI 片段对齐。PaymentCompleted 事件方向。哪条 API 设计违反契约?① Inventory 暴露 PATCH /orders/{id}/status ② Order 调 POST /reservations 带幂等键 ③ Payment 回调用 callbackToken 验签
答:①。订单状态属 Order 上下文,Inventory 不应直接改 orders;应通过 API/事件让 Order 自行变更。②③ 符合契约与幂等/验签约定。
OpenAPI 缺少 Idempotency-Key header,却写「支持重复提交安全」。违反哪条约定?应改哪一段?
违反契约优先与预留接口幂等约定。应在 OpenAPI parameters 增加 header 定义,并在 POST 说明中引用;实现与文档保持一致。
目录:demos/basics-04-domain-api-lab/。验收:YAML 含 reservations 路径 + 响应码 201/409/422。