界线高低文、畛域

以订单零碎设计为例

问题空间:

  • 下单(能不能买、怎么买);

  • 未实现领取的订单的领取、勾销(要领取多少钱、不买了怎么办);

  • 已领取实现的订单的发货问题(什么仓库进行发货);

  • 发货订单的物流履约问题(仓库实现发货、物流签收、用户确认);

解决方案空间:畛域

实体Entity

特色:具备惟一的标识、可变性

  • 惟一标识:每个实体都具备惟一的标识,能够来自于数据库的ID或者是具备业务含意的复合标识,能够用于辨别不同实体;

  • 可变性:实体有显著的生命周期,在生命周期内,响应内部事件履行职责(解决方案),驱动自身的数据或状态的变动;

作用:落地解决方案

  • 实体的可变性取决于其具备的角色和职责,通过实体咱们能够内聚以后域内的业务逻辑,联合畛域实体资源库、畛域事件来长久化以及向其余域公布变更;

值对象ValueObject

特色:无唯一性标识、不可变、无副作用(无状态)

作用:实体或聚合的隶属属性

聚合Aggregate

将实体和值对象在一致性边界内组成聚合:

  • 在一致性边界内,实体数据具备强一致性(在这个边界内的行为都是原子的);

  • 正确的聚合设计特色是事务肯定是清晰的;

  • 防止臃肿聚合:将一个聚合切分为多个聚合甚至是是不同域;

准则:

  • 在一致性边界内建模真正的不变条件(比方父单拆单实现意味着子单肯定存在),这种不变条件同样是对畛域测试的重点;

  • 设计小聚合,臃肿的聚合是贫血模型的另一个极其,对性能、可测试性、隔离性、长期保护都是有侵害的;

  • 通过惟一标识援用其余聚合;

    咱们在议论聚合的一致性边界,如果聚合间接援用其余聚合及其容易让研发人员在具体开发过程中突破这一准则(比方在拍品这个聚合内间接操作拍卖流动这个聚合缩短流动完结工夫,这会导致不同的拍品所持有的流动聚合的不一致性);
  • 在边界之外应用最终一致性;

    采纳畛域事件的形式,向其余域公布变更,而不影响以后聚合边界内的事务提交;

总结:

  • 以后聚合变动强一致性;跨聚合变动,每个聚合外部谋求强一致性,不同聚合间谋求最终一致性;

  • 聚合(Aggregate)的职责执行,能够总结为在业务规定的束缚下,原子的扭转本身数据(DomainRepository),同时将本身的变动传递给其余关怀该变动的聚合(DomainEvent);

资源库DomainRepository

作用:一致性边界内的数据变动的长久化

查问聚合:

  • 不同于DAO,DAO面向的是数据,聚合查问面向的是实体(可能起源自多种存储实现下的数据);

  • 聚合的查问须要向应用聚合的中央暗藏,聚合查找的细节,这种分层也有助于咱们在聚合查问的中央建设数据变动的追踪或者通过惟一标识查问聚合的缓存优化;

长久化聚合:

稳固长久化的意义:

  • 面向数据的编程形式下,在业务规定变动过程中咱们很难始终确保边界内的一致性不被毁坏(这种毁坏甚至是有意识的,因为对数据的操作被扩散在各种业务代码中)。

  • 借助聚合咱们失去了实体层面的一致性保障,DomainRepository须要做的就是将聚合内的不同变动以一种稳固的形式进行长久化。

    这个过程中咱们须要警觉业务知识被扩散到长久化层,同时只有不变的长久层实现能力开释测试人员的精力,让他们聚焦于实体测试。咱们通过建设对实体变动的追踪(Change-Tracking),并且将变动的数据后果反馈至对应的存储层进行存储。

Change-Tracking 计划:

基于Snapshot的计划:当数据从DB里取出来后,在内存中保留一份snapshot,而后在数据写入时和snapshot比拟。 

基于Proxy的计划:当数据从DB里取出来后,通过weaving的形式将所有setter都减少一个切面来判断setter是否被调用以及值是否变更,如果变更则标记为Dirty。在保留时依据Dirty判断是否须要更新。

殷浩,公众号:技术琐话阿里技术专家详解DDD系列 第三讲 - Repository模式

Snapshot计划实际:

  1. 在聚合查问实现后,调用IChangeTraceableAggregate#attach函数建设数据快照;

// 聚合实现IChangeTraceableAggregate对应的attach/detach办法@DomainEntity@Slf4j@ToStringpublic abstract class AbstractPayingOrder implements IChangeTraceableAggregate<String, PayingOrderChangeInfo> { //订单信息 @Getter protected NewOrderDTOWithBLOBs orderDTO; // 通过自定义mybatis-generator-plugin插件生成数据层diff工具; protected transient NewOrderDTOWithBLOBs.Tracer orderDTOTracer; public AbstractPayingOrder(@NonNull NewOrderDTOWithBLOBs orderDTO) { this.orderDTO = orderDTO; // ... } @Override public void attach() { //追踪orderDTO变更 this.orderDTOTracer = this.orderDTO.new Tracer(); this.orderDTOTracer.attach(); } @Override public void detach() { if (this.orderDTOTracer != null) { this.orderDTOTracer.detach(); } }}//DomainRepository的查问聚合的函数执行后回调IChangeTraceableAggregate#attach建设snapshotpublic abstract class RepositorySupport<A extends IChangeTraceableAggregate<ID, D>, ID extends Serializable, D> implements IRepository<A, ID> { // find 回调 protected abstract A onFind(ID id); @Override public A find(@NonNull ID id) { A aggregate = this.onFind(id); if (aggregate != null) { // 这里的就是让查问进去的对象可能被追踪。 // 如果本人实现了一个定制查问接口,要记得独自调用attach。 aggregate.attach(); } return aggregate; } //...}

2.聚合职责函数执行实现后,DomainRepository基于IChangeTraceableAggregate#getChangeInfo()后果构建无业务侵入的长久化逻辑

// 聚合实现IChangeTraceableAggregate的获取增量变动的办法@DomainEntity@Slf4j@ToStringpublic abstract class PayingOrder implements IChangeTraceableAggregate<String, PayingOrderChangeInfo> { @Getter protected NewOrderDTOWithBLOBs orderDTO; protected transient NewOrderDTOWithBLOBs.Tracer orderDTOTracer; public AbstractPayingOrder(@NonNull NewOrderDTOWithBLOBs orderDTO) { this.orderDTO = orderDTO; // ... } //... @Override public PayingOrderChangeInfo getChangeInfo() { //调用diff工具返回增量变动,参看:https://gitlab.yit.com/backend/mybatis-generator-plugin/blob/master/src/main/java/com/yit/mybatisplugin/TracerPlugin.java NewOrderDTOWithBLOBs orderDTOWithBLOBs = this.orderDTOTracer.getChangeInfo(); if (orderDTOWithBLOBs != null) { orderDTOWithBLOBs.setUpdatedAt(new Date()); } return new PayingOrderChangeInfo(orderDTOWithBLOBs); }}//DomainRepository的update函数回调IChangeTraceableAggregate#getChangeInfo获取增量变动public abstract class RepositorySupport<A extends IChangeTraceableAggregate<ID, D>, ID extends Serializable, D> implements IRepository<A, ID> { @Override public void update(@NonNull A aggregate) { // 调用UPDATE this.onUpdate(aggregate.getChangeInfo()); // 从新追踪(清理旧快照,新建快照) aggregate.detach(); aggregate.attach(); }}@Componentpublic class PayingOrderRepository extends RepositorySupport<AbstractPayingOrder, String, PayingOrderChangeInfo> { @Override protected AbstractPayingOrder onFind(String orderNumber) { //... } @Override protected void onUpdate(PayingOrderChangeInfo payingOrderChangeInfo) { //长久化层不再关怀不同业务场景内变动的具体数据字段 if (payingOrderChangeInfo.getUpdateOrderDTO() != null) { orderRepository.updateByPrimaryKeySelective(payingOrderChangeInfo.getUpdateOrderDTO()); } }}

畛域事件

作用:一致性边界之外的不同聚合间的变动感知

留神点:畛域事件自身是值对象,所以切勿在事件中公布畛域对象;

事件模型实际:

基于eventBus的畛域事件公布器

//实体中生成畛域事件:public abstract class PayingOrder implements IChangeTraceableAggregate<String, PayingOrderChangeInfo> { public void pay(OrderPayCommand orderPayCommand){ //以后聚合数据变更 this.orderDTO.setState(OrderStateEnum.PAY_FINISH.getValue()); //生成畛域事件 addDomainEvent(new OrderPayFinishedEvent(this)); }}//长久化实现后公布事件:public class OrderDomainService { public void confirmOrder(String orderNumber){ ShippingOrder shippingOrder = shippingOrderRepository.find(orderNumber); shippingOrder.confirm(); //长久化 shippingOrderRepository.update(shippingOrder); //公布畛域事件 domainEventBus.publish(shippingOrder); }}//事件监听@Slf4j@Componentpublic class OrderPayFinishedEventHandler implements IDomainEventHandler<OrderPayFinishedEvent> { @Override @Subscribe public void handleDomainEvent(OrderPayFinishedEvent domainEvent) { //... }}

畛域服务

现实中的畛域服务代码模板:

业务逻辑被内聚在畛域实体内,在畛域服务中调度聚合资源库进行长久化,通过事件模型公布在聚合中生成的畛域事件。

@Servicepublic class DomainService{  @Autowired private DomainRepository domainRepository; @Autowired private DomainEventBus domainEventBus; public void action(Identity identity, Command command) { //1.锁定畛域实体 if(trylock(identity)){ //2.查问聚合 DomainEntity domainEntity = domainRepository.find(identity); //3.执行聚合操作 domainEntity.action(command); //4.长久化聚合变动,独立事务提交 domainRepository.update(domainEntity); //5.公布畛域事件 domainEventBus.publish(domainEntity); } }}

畛域服务实际:

@Servicepublic class PayingOrderPayDomainService { @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); AbstractPayingOrder payingOrder = redisLock.with(() -> { AbstractPayingOrder innerPayingOrder = payingOrderRepository.find(orderPayCommand.getOrderNumber()); //订单领取 innerPayingOrder.pay(orderPayCommand); //事务提交 transactionUtil.transaction(() -> payingOrderRepository.update(innerPayingOrder), Propagation.REQUIRES_NEW); return innerPayingOrder; }, () -> { throw new ServiceRuntimeException(OrderReturnCode.SYSTEM_BUSY_CODE); }); //公布畛域事件 domainEventBus.publish(payingOrder); }}

DDD测试

**准则
**

  • 聚焦实体,实体是业务变动的焦点,长久层、事件处理尽可能的轻或者稳固;

  • 检测聚合的不变性规定;

  • 检测过程中公布的畛域事件;

畛域测试实际

public class ShippingOrderTest{ @DataProvider public Iterator<Object> provideTestData() { List<Object> params = new ArrayList<>(); params.add(new DeliverySubOrderItemsSuccessParam("子订单未发货,一个子订单只有一个item,子单商品待发货", OrderStateEnum.PROCESSING.getValue(), OrderStatus.WAIT_SEND, OrderStatus.WAIT_RECEIVE, false, new ArrayList<SubOrderItemParam>() {{ add(new SubOrderItemParam(checkItemEntityId, 124478, OrderSkuLogisticsStatus.INIT, OrderSkuLogisticsStatus.ON_THE_WAY)); }}, new String[] {"SubOrderItemDeliveredEvent", "SubOrderDeliveredEvent"})); //... return params.iterator(); } @Test(description = "子订单商品发货测试", dataProvider = "provideTestData") public void testDeliverySubOrderItems(DeliverySubOrderItemsSuccessParam param) { //1、测试数据筹备 NewOrderDTOWithBLOBs orderDTO = new NewOrderDTOWithBLOBsBuilder().build(); List<NewSubOrderDTO> newSubOrderDTOList = Lists.newArrayList(new NewSubOrderDTOBuilder().subOrderStatus(param.subOrderStatus).subOrderState(param.subOrderState).build()); DeliverItemsCommand deliveryItemsCommand = new DeliveryItemsCommandBuilder().resetDeliveryInfo(param.resetDeliveryInfo).build(); //2 初始化ShippingOrder实体 ShippingOrder shippingOrder = new ShippingOrder(orderDTO, newSubOrderDTOList); //建设追踪 shippingOrder.attach(); //3 执行待测办法 shippingOrder.deliverSubOrderItems(deliveryItemsCommand); //4 通过changeInfo验证后果 ShippingOrderChangeInfo shippingOrderChangeInfo = shippingOrder.getChangeInfo(); //4.1 验证suborderchange SubOrderItemParam subOrderItemParam = param.subOrderItemParams.stream().filter(p -> p.id == checkItemEntityId).collect(Collectors.toList()).get(0); if(subOrderItemParam.subOrderItemStatus != OrderSkuLogisticsStatus.COMPLETED) { NewSubOrderItemDTO newSubOrderItemDTO = shippingOrderChangeInfo.getUpdateSubOrderItemDTOList().get(0); assertThat(newSubOrderItemDTO.getYitiaoLogisticsCreatedAt()).as("运单号更新工夫近1秒钟").isAfter(DateTime.now().plusSeconds(-2).toDate()); } else { assertThat(shippingOrderChangeInfo.getUpdateSubOrderItemDTOList().size() == 0).as("子订单商品没有更新").isTrue(); } //5 验证畛域事件列表 List<String> domainEventNames = shippingOrder.getDomainEvents().stream().map(p -> p.getClass().getName().replaceAll(".*.", "")).collect(Collectors.toList()); assertThat(domainEventNames).as("畛域事件").containsOnly(param.expectDomainEventNames); }}

转载请注明出处