共计 4917 个字符,预计需要花费 13 分钟才能阅读完成。
Aggregate(恪守不变性规定设计设计聚合边界,业务知识的内聚)
-
外围聚合设计
CQRS(读写隔离)
-
ReadonlyPayingOrder 用于解决领取环节内的读场景问题 (结构相较于 ReadWritePayingOrder 更轻量),例如收银台获取待付款金额、领取后果页获取领取后果信息
-
ReadWritePayingOrder 响应 PayingCommand、CancelCommand 等事件,解决订单领取、勾销相干问题
-
通过实体划分实现读写隔离,聚焦订单领取域内的问题(勾销、领取等)
Domain Event(一致性边界之外的通信解决方案,跨域通信)
-
实体函数中变更本身数据同时生成相干的畛域事件 (数据变动和事件都随同着实体,domain repository 以及 event handler 别离解决数据长久化以及畛域事件)
-
同时联合实体隔离达到事件隔离的指标,比方拼团订单领取实现的事件必须通过拼团订单实体来结构,可能从本源上防止事件净化问题;
/**
* 畛域实体
*/
@DomainEntity
@Slf4j
@ToString
public 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
@Component
public 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
@Repository
public 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
@Service
public 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(聚焦实体,通过畛域实体的职责解决简单的业务场景结构问题)
测试过程
-
根本数据筹备用于结构基准测试实体
-
通过基准测试实体的畛域函数模仿畛域生命周期内的不同状态
-
以不同的状态下的实体,验证聚合设计时的不变性规定以及相应的畛域事件
收益
因为咱们将业务都内聚到了畛域实体外部(从下面能够看到 service 和 repository 中曾经做到了无业务侵入,已不再是咱们的测试重点),因而咱们得以聚焦于实体测试。
以领取订单为例,咱们依照不同类型的订单咱们筹备了 11 个不同的订单实体,并通过执行畛域实体的 pay()、cancel() 或者模仿三方领取回调批改已领取金额来叠加影响(咱们能够自由组合失去不同类型的不同状态的订单),以笼罩了近 400 个测试场景。
转载请注明出处