欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!
在我后台回复 「资料」 可领取
编程高频电子书!
在我后台回复「面试」可领取硬核面试笔记!文章导读地址:点击查看文章导读!
感谢你的关注!
基于电商履约场景的 DDD 实战

基于 Cola 实现电商履约架构设计
我们在实际进行架构设计的时候,不用每一个方法都严格按照 Cola 或者 DDD 去设计,可以根据自己的需求以及习惯,做出一些合理的变动
这里将履约这个模块分为 6 个部分(后两个模块不重要,主要是前 4 个模块要清楚每一个模块的作用):

- api:与外界负责交互的模块
- application:原来的 app 层,负责全局服务的处理
- domain:领域层
- infrastructure:基础设施层
- rpc:与其他模块进行 rpc 通信的接口
- start:启动 SpringBoot 项目的模块
订单履约流程
订单履约的流程,当订单支付成功之后,订单上下文会发送一个【订单支付成功】的事件,在履约上下文中,就可以监听【订单支付成功】事件,之后开始履约的流程
- 首先会进入到 api 层
监听器放在 api 层,与外界进行交互,监听器收到事件之后,将外部的事件转为 Command,再调用 application 层提供的【应用服务】来进行处理
api --
@Component
@Slf4j
public class OrderPayedEventListener implements MessageListenerConcurrently {
@Autowired
private FulfillApplicationService fulfillApplicationService;
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
try {
for (MessageExt messageExt : list) {
String message = new String(messageExt.getBody());
log.info("OrderPayedEventListener message:{}", message);
// 1、将message转化为OrderPayedEvent领域事件
OrderPayedEvent orderPayedEvent = JSONObject.parseObject(message, OrderPayedEvent.class);
// 2、把领域事件,转换为一个command
OrderFulfillCommand orderFulfillCommand = buildCommand(orderPayedEvent);
// 3、交给app层的应用服务逻辑,去推动命令的流程编排和执行
fulfillApplicationService.executeOrderFulfill(orderFulfillCommand);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
log.error("consumer error", e);
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
}
- application 层
在 application 层,就通过 Executor 执行履约的操作
@Component
public class FulfillApplicationService {
@Autowired
private OrderFulfillCommandExecutor orderFulfillCommandExecutor;
public void executeOrderFulfill(OrderFulfillCommand orderFulfillCommand) {
// 通过 Executor 来进行履约的处理
orderFulfillCommandExecutor.execute(orderFulfillCommand);
}
}
这里我们将 Executor 的具体实现也放在 application 层中去,在 Executor 中实现具体的履约流程:
-
首先是
保存订单,这里先将 Command 给转成履约单 FulfillOrder,再将履约单进行保存操作,并且插入日志这里保存履约单的操作写到了 Gateway 中去,因为 Gateway 就是与下层 Infrastructure 层交互的
保存订单之后通过 【fulfillOrderLogDomainService】 来插入操作日志,这里将日志的插入操作写入到了 【fulfillOrderLogDomainService】 (即履约订单日志领域服务)中去,在这个 【DomainService】 中再调用 Gateway 去和 Infrastructure 进行交互,因为这里在插入日志信息的时候,还有一些额外的信息需要设置一下,所以在 DomainService 层进行了设置,之后再通过 Gateway 进行保存,可以保证 Gateway 层的职责比较单一(仅仅和 Infrastructure 交互或者其他模块交互)
-
其次是
风控拦截,风控拦截已经是其他模块的功能了,毫无疑问使用 Gateway 来和其他上下文进行通信,这里在 Gateway 中就可以直接和风控模块暴露的 rpc 接口进行通信了(使用 Gateway 层解耦)如果该订单被风控拦截了,使用【domainEventGateway】来发布被拦截的领域事件
-
接下来是
预分仓,由于预分仓这里,我们需要一些距离计算的方法来选择合适的仓库,而 Gateway 的目的是和其他模块进行交互,功能比较单一,因此预分仓这里我们再去创建一个【warehouseDomainService】即仓库的领域服务,在仓库的领域服务中再通过 Gateway 和仓库交互来获取所有仓库并且进行仓库的选择预分仓之后通过 【fulfillOrderLogDomainService】 来插入操作日志
-
下来是
分物流,这里和预分仓是类似的,分物流中的操作也比较复杂,所以不能直接放到 Gateway 中去,要先创建一个【logisticsDomainService】物流的领域服务,在物流的领域服务中去执行分物流的一系列操作分物流之后通过 【fulfillOrderLogDomainService】 来插入操作日志
-
最后是
下库房,先通过 Gateway 来和库房上下文进行交互,调用库房上下文暴露的接口来保存履约单下库房之后通过 【fulfillOrderLogDomainService】 来插入操作日志
@Component
@Slf4j
public class OrderFulfillCommandExecutor {
// ...
// 专门负责订单履约流程的编排,把这个流程按照战术建模的设计,完成落地开发
@Transactional(rollbackFor = Exception.class)
public void execute(OrderFulfillCommand orderFulfillCommand) {
// 第一步,保存订单,需要去使用履约订单仓储/gateway来进行保存
FulfillOrder fulfillOrder = fulfillOrderFactory.createFulfillOrder(orderFulfillCommand);
log.info("创建fulfillOrder实体:{}", JSONObject.toJSONString(fulfillOrder));
fulfillOrderGateway.save(fulfillOrder);
fulfillOrderLogDomainService.saveOrderCreatedLog(fulfillOrder);
// 第二步,风控拦截
Boolean interceptResult = riskControlApiGateway.riskControlIntercept(fulfillOrder);
log.info("风控拦截:{}", JSONObject.toJSONString(interceptResult));
if (!interceptResult) {
fulfillOrder.riskReject();
// 如果被风控拦截了,此时就需要发布订单被拦截的领域事件,通知人工审核
domainEventGateway.publishOrderInterceptedEvent(new OrderInterceptedEvent(fulfillOrder.getFulfillId().getFulfillId()));
return;
}else {
fulfillOrder.riskPass();
}
// 第三步,预分仓
Warehouse warehouse = warehouseDomainService.preAllocateWarehouse(fulfillOrder);
log.info("预分仓:{}", JSONObject.toJSONString(fulfillOrder.getFulfillOrderWarehouse().getWarehouseId()));
fulfillOrderLogDomainService.saveOrderWarehousedLog(fulfillOrder);
// 第四步,分物流
logisticsDomainService.allocateLogistics(fulfillOrder, warehouse);
fulfillOrderLogDomainService.saveOrderLogisticsLog(fulfillOrder);
log.info("分物流:{}",fulfillOrder.getLogisticsOrder().getLogisticsId());
// 第五步,下发库房
warehouseApiGateway.sendFulfillOrder(fulfillOrder, warehouse);
fulfillOrderLogDomainService.saveOrderInStoredLog(fulfillOrder);
log.info("下发库房");
}
}
上边的履约流程总共有 5 个步骤,这里只挑选两个具有代表性(Gateway 分别和 Infrastructure、其他模块交互的两种情况)的步骤说一下:
这里先说一下第一步:保存订单,在保存订单的时候,需要两步:
1、通过 【fulfillOrderGateway】 与 Infrastructure 交互,进行订单的保存
2、通过 【fulfillOrderLogDomainService】 进行日志的插入操作
这里在 Gateway 中直接调用了 Infrastructure 层的 DAO 进行数据库操作,整个流程就结束了
@Component
public class FulfillOrderGatewayImpl implements FulfillOrderGateway {
@Override
public void save(FulfillOrder fulfillOrder) {
FulfillOrderDO fulfillOrderDO = fulfillOrderDOConverter.convert(fulfillOrder);
List<FulfillOrderItemDO> fulfillOrderItemDOs = mapStructSupport.convertToFulfillOrderItemDOs(fulfillOrder.getFulfillOrderItems());
fulfillOrderDAO.save(fulfillOrderDO);
fulfillOrderItemDAO.saveBatch(fulfillOrderItemDOs);
}
}
接下来再看一下第二步:风控拦截,需要 2 步:
1、通过 【riskControlApiGateway】与风控模块进行交互,判断当前订单是否要被拦截
2、如果被拦截了,需要通过【domainEventGateway】发布【订单被拦截的事件】
这里在 Gateway 中直接就引入了风控模块的 API,通过 RPC 直接调用
@Component
public class RiskControlApiGatewayImpl implements RiskControlApiGateway {
@DubboReference(version = "1.0.0", retries = 0)
private RiskApi riskApi;
@Override
public Boolean riskControlIntercept(FulfillOrder fulfillOrder) {
// 通过 Converter 将 fulfillOrder 转为发往风控拦截的请求 FulfillOrderRiskRequest
FulfillOrderRiskRequest fulfillOrderRiskRequest = fulfillOrderRiskRequestConverter.convert(fulfillOrder);
// 调用风控拦截的 API 判断是否拦截
FulfillOrderRiskDTO fulfillOrderRiskDTO = riskApi.fulfillOrderRiskControlIntercept(fulfillOrderRiskRequest);
if(fulfillOrderRiskDTO == null) {
log.warn("风控调用失败");
return false;
}
if(!fulfillOrderRiskDTO.getRiskResult()) {
log.warn("风控检查拒绝通过");
return false;
}
return true;
}
}
上边代码比较多,可能看着不太清晰,这里我画张图:

这里还涉及一些实体对象的转换,监听器收到事件的时候,拿到的是消息,先将消息转为【OrderFulfillCommand】之后,再传入 Application 层,再将 【Command】转为【FulfillOrder】之后,传入 Domain 层,再转为【FulfillOrderDO】传入 Infrastructure 层
【OrderFulfillCommand】-> 【FulfillOrder】 使用 FulfillOrderFactory 来进行转换了,通过这个 Factory 创建一个履约单对象
【FulfillOrder】 ->【FulfillOrderDO】 使用 Converter 来进行转换,Converter 都放在了 Infrastructure 层
至此,订单履约流程的架构设计就完成了
那么可以发现,整个流程,从上到下,api -> application -> domain -> infrastructure,每个层的职责都是很清晰的,但是也正是因为职责划分的很清晰,从而导致代码在写起来的时候,需要比 MVC 架构多写很多的内容
因此对于中小型项目来说,盲目引入 DDD 只会导致项目的工作量剧增,DDD 的目的就是前期通过良好的分层、建模,后期可以降低维护成本



















