4. 支付上下文边界:限界上下文在支付系统中的划分
在领域驱动设计中,限界上下文(Bounded Context)是划分系统边界的基本单元。支付系统作为典型的金融核心域,其内部包含多个职责清晰、演化速度不同的子领域。错误的边界划分会导致模型污染、团队耦合、部署僵化。本章从支付业务本质出发,给出经过生产验证的上下文划分方案。
4.1 支付系统的核心限界上下文
一个完整的支付订单聚合系统,通常包含以下 6 个限界上下文。每个上下文拥有独立的通用语言、数据库和部署单元。
| 限界上下文 | 核心职责 | 典型聚合根 | 数据存储 |
|---|---|---|---|
| 订单上下文 | 管理交易订单生命周期、状态机、金额校验 | PaymentOrder | MySQL(强一致) |
| 支付执行上下文 | 对接外部渠道、发起扣款、处理异步回调 | Payment | MySQL + 本地消息表 |
| 对账上下文 | 拉取渠道对账单、逐笔匹配、差异处理 | Reconciliation | MySQL + 对象存储 |
| 退款上下文 | 处理退款申请、逆向资金、退款状态机 | Refund | MySQL(独立库) |
| 账户上下文 | 管理用户余额、冻结/解冻、流水记录 | Account | MySQL(分库分表) |
| 通知上下文 | 商户回调、站内信、Webhook 重试 | Notification | Redis + MySQL |
实践要点:订单上下文与支付执行上下文是最容易混淆的一对边界。订单关注"这笔交易该不该付、付多少、付给谁";支付执行关注"怎么付、渠道返回什么、失败怎么重试"。两者必须分离,否则渠道变更会污染订单模型。
4.2 上下文划分的三大原则
- 业务变化频率分离:订单上下文变化最慢(业务规则稳定),支付执行上下文变化最快(渠道对接频繁)。将快变与慢变隔离,避免频繁发布影响核心订单逻辑。
- 数据一致性边界:强一致性需求(如订单金额与支付金额校验)放在同一上下文内;最终一致性需求(如对账、通知)通过领域事件跨上下文传递。
- 团队结构对齐:每个限界上下文对应一个独立的小团队(2-4 人),拥有从模型到数据库的完整决策权。
4.3 上下文映射与集成关系
下图展示了支付系统中 6 个限界上下文之间的协作关系。箭头方向表示依赖或事件流向,而非数据复制方向。
关键设计决策:订单上下文与支付执行上下文之间使用领域事件而非 RPC 调用。订单发布 PaymentOrderCreated 事件,支付执行上下文订阅后创建 Payment 聚合。这种异步解耦使得两个上下文可以独立扩缩容,且支付执行上下文的故障不会阻塞订单创建。
4.4 上下文之间的通信模式
限界上下文之间的通信必须显式设计,避免隐式共享数据库或模型。支付系统中常用的三种模式:
- 防腐层(ACL):当订单上下文需要查询支付执行上下文的渠道信息时,通过 ACL 转换模型,防止外部模型污染订单领域。
- 领域事件(Domain Event):支付完成、退款成功等关键状态变更,通过事件总线广播。事件结构应包含业务 ID、事件类型、时间戳和必要载荷,不携带内部聚合细节。
- 开放主机服务(OHS):对账上下文对外暴露 ReconciliationService 接口,供运营后台调用,接口参数使用对账上下文自己的值对象。
4.5 边界划分的常见陷阱
陷阱一:共享数据库导致模型泄漏
两个上下文共用同一张 payment_order 表,订单上下文修改了表结构,导致支付执行上下文查询失败。解决方案:每个上下文拥有独立的数据库 schema,即使物理上在同一实例,逻辑上也要通过 schema 或数据库名隔离。
两个上下文共用同一张 payment_order 表,订单上下文修改了表结构,导致支付执行上下文查询失败。解决方案:每个上下文拥有独立的数据库 schema,即使物理上在同一实例,逻辑上也要通过 schema 或数据库名隔离。
陷阱二:过度拆分导致分布式事务爆炸
将支付执行拆分为"渠道选择"、"扣款发起"、"回调处理"三个上下文,导致一次支付需要跨三个上下文协调。解决方案:将强相关的流程放在同一上下文内,支付执行上下文内部可以包含多个模块,但对外只暴露一个聚合根。
将支付执行拆分为"渠道选择"、"扣款发起"、"回调处理"三个上下文,导致一次支付需要跨三个上下文协调。解决方案:将强相关的流程放在同一上下文内,支付执行上下文内部可以包含多个模块,但对外只暴露一个聚合根。
4.6 上下文边界在 Go 项目中的落地
在 Go 项目中,每个限界上下文对应一个独立的 module 或 package 层级。推荐的项目结构如下:
payment-system/
├── order-context/ # 订单上下文
│ ├── domain/ # 领域模型、聚合根、值对象
│ ├── application/ # 应用服务、DTO
│ ├── infrastructure/ # 数据库、事件发布
│ └── interfaces/ # API、gRPC 端点
├── payment-execution-context/ # 支付执行上下文
│ ├── domain/
│ ├── application/
│ ├── infrastructure/
│ └── interfaces/
├── reconciliation-context/ # 对账上下文
├── refund-context/ # 退款上下文
├── account-context/ # 账户上下文
├── notification-context/ # 通知上下文
└── common/ # 共享内核(仅限值对象、基础类型)
Go 实践建议:每个上下文内部使用
internal 目录限制包导出范围,防止跨上下文直接引用领域模型。上下文之间的通信统一通过 interfaces 层暴露的 gRPC 或消息队列接口进行,禁止在 domain 层直接 import 其他上下文的包。