Aggregate(恪守不变性规定设计设计聚合边界,业务知识的内聚)

  • 外围聚合设计


CQRS(读写隔离)

  • ReadonlyPayingOrder用于解决领取环节内的读场景问题(结构相较于ReadWritePayingOrder更轻量),例如收银台获取待付款金额、领取后果页获取领取后果信息

  • ReadWritePayingOrder响应PayingCommand、CancelCommand等事件,解决订单领取、勾销相干问题

  • 通过实体划分实现读写隔离,聚焦订单领取域内的问题(勾销、领取等)

Domain Event(一致性边界之外的通信解决方案,跨域通信)

  • 实体函数中变更本身数据同时生成相干的畛域事件(数据变动和事件都随同着实体,domain repository以及event handler别离解决数据长久化以及畛域事件)

  • 同时联合实体隔离达到事件隔离的指标,比方拼团订单领取实现的事件必须通过拼团订单实体来结构,可能从本源上防止事件净化问题;

/** * 畛域实体 */@DomainEntity@Slf4j@ToStringpublic abstract class AbstractReadWritePayingOrder extends AbstractPayingOrder implements IChangeTraceableAggregate<String, PayingOrderChangeInfo> { /** * 订单领取 */ public void pay(OrderPayCommand orderPayCommand) { if (OrderState.isCanceled(this.orderDTO.getState())) { log.warn("order:{} is canceled! orderPayCommand:{}", orderPayCommand.getOrderNumber(), orderPayCommand); } else if (OrderState.isPaid(this.orderDTO.getState())) { //幂等 addDomainEvent(new OrderPayFinishedEvent(this)); } else if (OrderState.isInPaying(this.orderDTO.getState())) { if (isPayFull()) { //全额领取实现 //生成领取实现事件 addDomainEvent(new OrderPayFinishedEvent(this)); } else { //局部领取实现... } } //超额领取检测 if (isOverPaid()) { addDomainEvent(new OverPaidEvent(this)); } } public void cancel(OrderCancelCommand orderCancelCommand) { //... }}
/** * 畛域事件处理,跨域通信(可实现为接口调用、音讯发送) */@Slf4j@Componentpublic class OrderPayFinishedEventHandler implements IDomainEventHandler<OrderPayFinishedEvent> { @Autowired private KafkaJsonMessageService kafkaJsonMessageService; @Override @Subscribe public void handleDomainEvent(OrderPayFinishedEvent domainEvent) { //订单整体领取实现 kafkaJsonMessageService.publish(QueueConfig.ORDER_PAY_FINISH_TOPIC, new Gson().toJson(domainEvent.getSimpleOrderInfo())); }}

Domain Repository(面向聚合、实体设计而非数据)

@Slf4j@Repositorypublic class PayingOrderRepository extends RepositorySupport<AbstractReadWritePayingOrder, String, PayingOrderChangeInfo> { /** * 向上裸露聚合实体,暗藏聚合的数据获取形式  */ @Override public AbstractReadWritePayingOrder onFind(String orderNumber) { return PayingOrderFactory.createPayingOrder(orderDTO, orderItemDTOs, totalPaidAmoint, productInfos); } public ReadonlyPayingOrder findReadonlyPayingOrder(String orderNumber) { return PayingOrderFactory.createReadonlyPayingOrder(orderDTO, orderItemDTOs, totalPaidAmount); } /** * 最小常识法令,通过变更追踪的技术计划结构稳固的长久化解决方案,从而不关怀畛域函数内具体变动的是什么字段,防止业务侵入 */ @Override @Transactional(rollbackFor = Exception.class) public void onUpdate(PayingOrderChangeInfo payingOrderChangeInfo) { //订单更新 if (payingOrderChangeInfo.getUpdateOrderDTO() != null) { orderRepository.updateByPrimaryKeySelective(payingOrderChangeInfo.getUpdateOrderDTO()); } //操作日志保留 if (CollectionUtils.isNotEmpty(payingOrderChangeInfo.getInsertOrderChangeLog())) { orderHistory.batchInsert(payingOrderChangeInfo.getInsertOrderChangeLog()); } }}

Snapshot计划实际:在聚合查问实现后,调用IChangeTraceableAggregate#attach函数建设数据快照;

答案在风中,公众号:答案在风中的BlogDDD实际落地(二)

Domain Service(畛域对象的调度、低业务侵入)

  • domain service 调度畛域对象的职责,但不感知其具体实现细节

@Slf4j@Servicepublic class PayingOrderDomainService { @Autowired private PayingOrderRepository payingOrderRepository; @Autowired private DomainEventBus domainEventBus; @Autowired private RedisLockService mainRedisLockService; @Autowired private TransactionUtil transactionUtil; /** * 领取事件处理 */ public void pay(OrderPayCommand orderPayCommand) { RedisLock redisLock = mainRedisLockService.buildLock(ShippingOrderDomainService.LOCK_SCOPE_ORDER, orderPayCommand.getOrderNumber(), ShippingOrderDomainService.LOCK_DEFAULT_TTL); AbstractReadWritePayingOrder payingOrder = redisLock.withr(() -> { AbstractReadWritePayingOrder innerPayingOrder = orderPayCommand.isFreePay() ? payingOrderRepository.findOrderForFreePay(orderPayCommand) : payingOrderRepository.find(orderPayCommand.getOrderNumber()); //订单领取 innerPayingOrder.pay(orderPayCommand); //事务优先提交 transactionUtil.transaction(() -> payingOrderRepository.update(innerPayingOrder), Propagation.REQUIRES_NEW); return innerPayingOrder; }, () -> { throw new ServiceRuntimeException(OrderReturnCode.SYSTEM_BUSY_CODE, orderPayCommand.toString()); }); domainEventBus.publish(payingOrder); } /** * 勾销订单 */ public void cancelOrder(@NonNull OrderCancelCommand orderCancelCommand) { RedisLock redisLock = mainRedisLockService.buildLock(ShippingOrderDomainService.LOCK_SCOPE_ORDER, orderCancelCommand.getOrderNumber(), ShippingOrderDomainService.LOCK_DEFAULT_TTL); AbstractReadWritePayingOrder payingOrder = redisLock .withr(() -> { AbstractReadWritePayingOrder innerPayingOrder = payingOrderRepository.find(orderCancelCommand.getOrderNumber()); //勾销订单 innerPayingOrder.cancel(orderCancelCommand); //长久化 transactionUtil.transaction(() -> payingOrderRepository.update(innerPayingOrder), Propagation.REQUIRES_NEW); return innerPayingOrder; }, () -> { throw new ServiceRuntimeException(OrderReturnCode.SYSTEM_BUSY_CODE, orderCancelCommand.toString()); }); //发送畛域事件 domainEventBus.publish(payingOrder); }}

Domain Test(聚焦实体,通过畛域实体的职责解决简单的业务场景结构问题)

测试过程

  1. 根本数据筹备用于结构基准测试实体

  2. 通过基准测试实体的畛域函数模仿畛域生命周期内的不同状态

  3. 以不同的状态下的实体,验证聚合设计时的不变性规定以及相应的畛域事件

收益

因为咱们将业务都内聚到了畛域实体外部(从下面能够看到service和repository中曾经做到了无业务侵入,已不再是咱们的测试重点),因而咱们得以聚焦于实体测试。

以领取订单为例,咱们依照不同类型的订单咱们筹备了11个不同的订单实体,并通过执行畛域实体的pay()、cancel()或者模仿三方领取回调批改已领取金额来叠加影响(咱们能够自由组合失去不同类型的不同状态的订单),以笼罩了近400个测试场景。

转载请注明出处