3. 支付领域通用语言(Ubiquitous Language)
在领域驱动设计中,通用语言(Ubiquitous Language)是团队(领域专家、开发、产品、测试)共享的一套精确、无歧义的术语体系。支付领域涉及资金流转、风险控制、多方对账,术语的混乱将直接导致模型错误和线上资损。本章定义支付、订单、交易、账户、商户、渠道等核心术语及其关系,作为后续聚合建模和服务划分的语义基础。
3.1 核心术语定义
下表列出支付领域最核心的 6 个术语,每个术语都附带业务定义、DDD 映射建议和典型反例(避免误用)。
| 术语 | 业务定义 | DDD 映射建议 | 常见误用 |
|---|---|---|---|
| 支付(Payment) | 买家为获取商品或服务,向卖家转移资金所有权的行为。包含支付指令、支付方式、支付结果。 | 聚合根(Aggregate Root),承载支付流程状态机 | 将“支付”等同于“交易”,忽略支付单的生命周期 |
| 订单(Order) | 买家与商户之间达成的交易契约,记录商品/服务明细、金额、收货信息等。 | 独立聚合根,与支付聚合通过订单ID关联 | 在订单实体中直接嵌入支付状态字段 |
| 交易(Transaction) | 资金在支付链路中发生的每一次原子性操作记录。例如:扣款、退款、冻结、解冻。 | 值对象(Value Object)或实体,取决于是否需要独立生命周期 | 将“交易记录”与“支付单”混为一谈 |
| 账户(Account) | 资金存放的容器,包含余额、冻结金额、可用余额。可以是用户账户、商户账户、平台账户。 | 聚合根,账户操作必须通过领域服务保证一致性 | 在支付服务中直接修改账户余额 |
| 商户(Merchant) | 在支付平台上注册并接受支付的商业实体。包含商户号、结算配置、费率、资质信息。 | 聚合根,管理商户入驻、费率变更、结算规则 | 将商户信息作为订单的一个属性 |
| 渠道(Channel) | 实际执行资金划转的外部支付网关或银行接口。例如:支付、支付宝、银联。 | 聚合根或领域服务,封装渠道协议、路由、重试策略 | 在支付逻辑中硬编码渠道调用代码 |
术语边界提示: 支付(Payment)是业务行为,交易(Transaction)是资金记录。一次支付可能包含多笔交易(如:支付成功 + 退款交易)。在建模时,支付聚合根负责编排交易记录,而不是反过来。
3.2 术语关系图
下图展示 6 个核心术语之间的关联关系。箭头方向表示“依赖”或“引用”方向,数字表示多重性。
3.3 术语关系矩阵
为了更清晰地展示术语之间的交互关系,下表以“行→列”方向表示“依赖/引用”关系。✓ 表示存在直接关系,— 表示无直接关系。
| → 依赖方 / 被依赖方 ↓ | 支付 | 订单 | 交易 | 账户 | 商户 | 渠道 |
|---|---|---|---|---|---|---|
| 支付 | — | ✓ 引用订单ID | ✓ 包含交易 | — | — | ✓ 选择渠道 |
| 订单 | — | — | — | — | ✓ 归属商户 | — |
| 交易 | ✓ 属于支付 | — | — | ✓ 更新账户 | — | ✓ 通过渠道执行 |
| 账户 | — | — | — | — | ✓ 商户结算账户 | — |
| 商户 | — | ✓ 拥有订单 | — | ✓ 拥有账户 | — | ✓ 配置渠道 |
| 渠道 | — | — | — | — | — | — |
建模决策点: 支付与订单之间通过“订单ID”进行值引用,而不是对象嵌套。这保证了两个聚合根可以独立演进——订单关注商品履约,支付关注资金流转。如果直接嵌套,会导致聚合边界模糊,事务范围过大。
3.4 通用语言在代码中的体现
通用语言必须落实到代码模型中。以下是一个支付聚合根的简化代码示例,展示术语如何映射为类名、方法名和参数名。
// 支付聚合根 —— 对应通用语言中的 Payment
public class Payment : AggregateRoot<PaymentId>
{
// 值引用订单ID,不嵌套订单对象
public OrderId OrderId { get; private set; }
// 支付方式:WECHAT_PAY, ALIPAY, UNION_PAY
public PaymentMethod Method { get; private set; }
// 支付金额(单位:分)
public Money Amount { get; private set; }
// 支付状态机:INIT → PROCESSING → SUCCESS / FAILED
public PaymentStatus Status { get; private set; }
// 交易记录集合 —— 值对象
private List<Transaction> _transactions = new();
public IReadOnlyList<Transaction> Transactions => _transactions;
// 领域行为:发起支付
public PaymentResult Execute(Channel channel, Account buyerAccount)
{
// 1. 校验状态
// 2. 调用渠道执行扣款
// 3. 记录交易
// 4. 更新账户余额(通过领域事件通知)
// 5. 更新支付状态
}
// 领域行为:退款
public RefundResult Refund(Money refundAmount, Account buyerAccount)
{
// 校验退款金额不超过可退金额
// 记录退款交易
// 更新账户余额
}
}
反模式警告: 不要在支付聚合根中直接引用账户对象或商户对象。账户和商户是独立的聚合根,应该通过领域事件或领域服务进行协作。直接引用会导致跨聚合的事务耦合,破坏微服务边界。
3.5 术语使用规范
为了保持通用语言的一致性,团队需要遵守以下规范:
- 禁止同义词混用:例如“支付单”和“支付记录”统一为“支付”;“流水”和“交易记录”统一为“交易”。
- 禁止跨层借用:数据库表名、API 接口名、UI 文案必须与通用语言保持一致。例如数据库表名使用
payment而非pay_info。 - 禁止属性污染:订单聚合中不应出现“支付状态”字段。支付状态是支付聚合的职责,订单只关心“是否已支付”,通过查询支付聚合获取。
- 术语变更流程:任何术语修改必须经过领域专家确认,并同步更新到代码、文档、测试用例和 API 文档中。
3.6 术语与限界上下文映射
下图展示核心术语在微服务架构中的限界上下文分布。每个上下文拥有自己的通用语言子集,上下文之间通过事件或API通信。
上下文映射要点: 订单上下文通过发布
PaymentCompleted 领域事件来通知订单支付成功,而不是直接调用订单服务修改状态。账户上下文监听 AccountUpdated 事件进行余额变更。这种事件驱动方式保证了每个上下文的独立性和最终一致性。
3.7 术语一致性检查清单
在代码评审和模型评审中,使用以下清单检查通用语言的一致性:
- ☐ 所有聚合根名称是否与通用语言一致?
- ☐ 跨聚合引用是否使用ID而非对象?
- ☐ 领域事件名称是否使用过去时态(如
PaymentCompleted)? - ☐ 数据库表名、字段名是否与通用语言映射?
- ☐ API 路径是否使用术语(如
/payments而非/pay)? - ☐ 是否存在同义词混用(如“流水”和“交易记录”)?
- ☐ 是否存在跨上下文的状态字段冗余?
通过以上定义和规范,团队可以在支付、订单、交易、账户、商户、渠道之间建立清晰的语义边界,为后续的聚合划分和服务通信奠定坚实的语言基础。