领域驱动设计:如何在领域层处理外部支付网关?
符合DDD原则的支付意图架构方案
针对你遇到的三个核心困境,我们可以通过分层职责明确化、领域逻辑封装回领域层、严格保障实体创建有效性三个方向调整架构,同时完全遵循DDD原则:
1. 核心思路梳理
你的业务流程的核心约束是:
- 内部
PaymentIntent必须依赖外部网关支付意图才能处于有效状态 - 外部网关调用前必须完成纯领域规则校验(无需依赖外部服务)
基于此,明确各层职责:
- 领域层:负责所有纯领域逻辑(规则校验、实体状态有效性保障),保持纯净不依赖外部服务
- 应用层:负责协调领域层、基础设施层(网关服务、仓库)的流程,不包含领域逻辑
- 基础设施层:负责与外部系统(支付网关)、数据库的交互
2. 具体架构调整方案
(1)将领域规则校验封装回领域层
你提到的TotalPaymentSplitsEqualsIntentAmountRule和PaymentSplitReceiversMustBeUniqueRule都是纯领域规则,完全可以封装在PaymentIntent实体内部,无需依赖外部服务。
修改PaymentIntent实体,添加前置校验和严格的创建逻辑:
export class PaymentIntent { // 实体核心属性 private readonly marketplaceId: string; private readonly customerId: string; private readonly amount: number; private readonly gatewayPaymentIntent: ExternalGatewayPaymentIntent; // 其他属性... // 前置规则校验:在创建实体前验证领域规则 static validatePreCreation(amount: number, paymentSplitsData: PaymentSplit[]) { const validationErrors: string[] = []; // 校验拆分金额总和等于意图金额 const totalSplits = paymentSplitsData.reduce((sum, split) => sum + split.amount, 0); if (totalSplits !== amount) { validationErrors.push("支付拆分总和必须与支付意图金额一致"); } // 校验拆分接收方唯一 const receiverIds = paymentSplitsData.map(split => split.receiverId); if (new Set(receiverIds).size !== receiverIds.length) { validationErrors.push("支付拆分接收方不能重复"); } if (validationErrors.length > 0) { throw new DomainValidationError(validationErrors); } } // 严格的实体创建方法:必须传入所有必要参数,确保创建即有效 static create(params: { marketplaceId: string; customerId: string; amount: number; paymentMethodType: string; gateway: string; businessEntityId: string; paymentSplitsData: PaymentSplit[]; gatewayPaymentIntent: ExternalGatewayPaymentIntent; }): PaymentIntent { // 确保外部网关支付意图存在 if (!params.gatewayPaymentIntent) { throw new Error("创建支付意图必须提供外部网关支付意图"); } // 可在此添加其他实体内部状态校验 const paymentIntent = new PaymentIntent(); // 为实体属性赋值... paymentIntent.marketplaceId = params.marketplaceId; paymentIntent.customerId = params.customerId; paymentIntent.amount = params.amount; paymentIntent.gatewayPaymentIntent = params.gatewayPaymentIntent; // 其他属性赋值... return paymentIntent; } } // 自定义领域验证错误类 export class DomainValidationError extends Error { constructor(public readonly errors: string[]) { super("领域规则验证失败"); } }
(2)调整应用服务的职责,仅做流程协调
你的PaymentIntentService属于应用服务,它的职责是串联流程,不应该包含领域逻辑。修改后,应用服务只负责:
- 调用领域层完成前置规则校验
- 调用基础设施层的网关服务创建外部支付意图
- 调用领域层创建有效的内部
PaymentIntent - 持久化实体并返回结果
调整后的应用服务代码:
export class PaymentIntentService { constructor( private readonly gatewayService: GatewayService, private readonly paymentIntentRepository: PaymentIntentRepository ) {} async create({ marketplaceId, gateway, customerId, amount, paymentMethodType, paymentSplitsData, }: CreatePaymentIntentParams) { // 1. 调用领域层做规则校验,领域逻辑完全留在领域层 PaymentIntent.validatePreCreation(amount, paymentSplitsData); // 2. 调用基础设施层的网关服务,创建外部支付意图 const externalGatewayPaymentIntent = await this.gatewayService.createPaymentIntent({ amount, paymentMethodType, gatewayCustomerId: customerId, gateway }); // 3. 创建内部支付意图,确保传入所有必要参数,实体创建即有效 const paymentIntent = PaymentIntent.create({ marketplaceId, customerId, amount, paymentMethodType, gateway, businessEntityId: '', paymentSplitsData, gatewayPaymentIntent: externalGatewayPaymentIntent }); // 4. 持久化实体 await this.paymentIntentRepository.create(paymentIntent); return paymentIntent; } }
3. 针对三个困境的解决说明
- 向实体注入服务:所有校验规则都是纯领域逻辑,无需依赖外部网关服务,实体保持完全纯净,不依赖任何基础设施层服务
- 始终有效的领域模型:
PaymentIntent.create方法强制要求传入gatewayPaymentIntent,确保实体从创建那一刻起就处于完整、有效的状态,不存在中间无效状态 - 领域逻辑置于服务中:所有领域规则校验都封装在
PaymentIntent实体内部,应用服务仅做流程协调,完全避免了领域逻辑泄露到应用层的问题
内容的提问来源于stack exchange,提问作者Kainar Masujima
相关产品推荐
相关产品推荐

