领域驱动架构设计之事件篇

前言

领域驱动设计基于CQRS的事件架构, 可以使命令发起者和命令执行者解耦。通过一系列的事件的追加存储,可以对事件的追踪和溯源。采用事件架构模式,更加面向与业务职能,将复杂的业务场景拆分成不同事件执行,在一定程度上达到解耦和复用的目的。事件的发起者和执行分离,解耦下游的相关的系统,下游只需要监听关注的事件即可。

概念解释

Command:命令对象。
Event:事件接口。
EventStore:事件存储。
EventBus:消息总线。一般可以用MQ。
EventBody:事件存储实体。
INotificationPublisher:事件发布者。
INotificationListener:事件订阅消息者。
EventSourcingInfoDO:事件存储DO对象。
EventPublishedTrackerDO:事件发布追踪者DO对象。

总体设计架构图

事件驱动架构设计

事件持久化对象设计

EventPublishedTrackerDO

点击查看代码
public class EventPublishedTrackerDO {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    private Long id;

    /**
     * 事件溯源编号
     */
    private Long mostRecentNotifictionId;

    /**
     * 事件溯源event_id
     */
    private String mostRecentTrackerId;

    /**
     * 以标记名称作为事件发送分割标记
     */
    private String tagName;
}

EventSourcingInfoDO

点击查看代码

public class EventSourcingInfoDO  {

  private static final long serialVersionUID = 1L;

  /**
   * 主键
   */
  private Long id;

  /**
   * 事件标识
   */
  private String eventId;

  /**
   * 标记名称-一般为表名或者领域名称
   */
  private String tagName;

  /**
   * 持久化编号-每张表的主键或者领域标识
   */
  private String persistenceId;

  /**
   * 事件类型
   */
  private String eventType;

  /**
   * 事件实体
   */
  private byte[] eventBody;

  /**
   * 事件版本
   */
  private Integer eventVersion;

  /**
   * 事件发送者名称
   */
  private String sender;

  /**
   * 事件发送者ID
   */
  private Long senderId;

  /**
   * 事件状态
   */
  private Integer eventState;

}

构建Command和Event

首先抽象command命令对象

点击查看代码
public abstract class AbstractDomainCommand implements DomainCommand {
  @Default
  private Date occurredOn = new Date();//发起时间
  private UserId commder;//发起用户
}

interface DomainCommand {

  Date occurredOn();

  UserId commder();
}

接下来,我们以一个商品销项税的变更举例,销项税变更的ProductTaxCodeUpdateCommand对象

点击查看代码

public class ProductTaxCodeUpdateCommand extends AbstractDomainCommand {
  private ProductId productId;//商品实体ID类
  private String productTaxCode;//税码如:13
  public ProductTaxCodeUpdateEvent toEvent(ProductAggregateRoot product) {
    ProductTaxCodeUpdateEvent event = new ProductTaxCodeUpdateEvent();
    event.eventBody(
        ProductTaxCodeUpdateEventBody
            .builder()
            .persistenceId(product.identify())
            .productTaxCode(product.productTaxCode())
            .build());
    event.sender(this.commder());
    return event;
  }

销项税SalesTax值对象

点击查看代码
//税码接口
public interface Tax {
  default TaxCodeId id() {return null;}
  default String code() {return null;}
  default BigDecimal tax() {return null; }
}
//通用值对象类
public class ValueObject implements Serializable {
  private static final long serialVersionUID = -5984778999781379523L;
}
//税码值对象
public class SalesTax extends ValueObject implements Tax {
  private static final long serialVersionUID = -4283492714059316843L;
  private TaxCodeId id;
  private String code;
  private BigDecimal tax;
}

税码更新事件ProductSaleTaxUpdatedEvent对象及ProductTaxCodeUpdateEventBody

点击查看代码
//领域事件接口
public interface DomainEvent<EventBody extends IEventBody> extends Serializable{

  DomainEventId eventId();

  String tag();

  String eventType();

  EventBody eventBody();

  Date occurredOn();

  UserId sender();

  Integer eventVersion();
}
public class ProductTaxCodeUpdateEvent extends ProdcutEvent<ProductTaxCodeUpdateEventBody> implements DomainEvent {
  private static final long serialVersionUID = -4770564663557952597L;
}
public class ProductTaxCodeUpdateEventBody extends ProdcutEventBody implements IEventBody{
  private String productTaxCode;
}

构建聚合根对象

商品聚合根对象ProductAggregateRoot

点击查看代码
//聚合根抽象父类
public abstract class AggregateRoot<DomainIdentifyType extends DomainIdentify> implements
    Serializable, IVersion {
  private DomainIdentifyType identify;//领域实体ID对象
  private Integer version;//版本
  private UserId holder;//操作用户
  }
  //商品聚合根对象
  public static class ProductAggregateRoot extends AggregateRoot<ProductId> {
         /**
     * 产品编码
     */
    private String productCode;
    /**
     * 产品名称
     */
    private String productName;
	 /**
     * 产品税务编码
     */
    private String productTaxCode;
	....
	    /**
     * 品牌编号,来自产品配置-品牌管理
     */
    private BrandId brandId;
	
    /**
     * 备案税率
     */
    private SalesTax salesTax;
	....
  }

构建领域服务

商品领域服务ProductService对象

点击查看代码
public final class ProductService {
   @autoAuired
   private  ProductDomainRepository repository;
   @autoAuired
   private IMediator mediator;
   ....
   //通过商品ID获取聚合根对象Option包装
   private Option<ProductAggregateRoot> getState(ProductId id) {
     return repository.getById(id);
   }
   //更新商品聚合根对象
   public static Try<ProductAggregateRoot> updateProductTaxCode(ProductAggregateRoot product
      , String productTaxCode) {
    return Try.success(product.toBuilder()
        .productTaxCode(productTaxCode)
        .build());
  }
 //聚合根操作用户持有者(通用方法)
 public static AggregateRoot build(AggregateRoot root, UserId holder) {
    return root.toBuilder().holder(holder).build();
  }
   //商品销项税变更
   public void when(ProductSaleTaxUpdateCommand aCommand) {
    getState(aCommand.productId())
        .peek(product -> {
          updateSalesTax(product, aCommand.salesTax())
              .map(product -> build(product, aCommand.commder()))
              .andThen(repository::update)
              .andThen(product -> mediator.saveAndPublish(aCommand.toEvent(product)))
              .onFailure(ThrowableConsumer.COMMON);
        });
  }
   ....

}

事件存储和发送

事件存储值对象StoredEvent及仓库接口IEventStore

点击查看代码
/**
 * 事件存储对象
 */
@Data
public class StoredEvent {

  /**
   * 事件标识
   */
  private String eventId;

  /**
   * 标记名称-一般为表名或者领域名称
   */
  private String tagName;

  /**
   * 持久化编号-每张表的主键或者领域标识
   */
  private String persistenceId;

  /**
   * 事件类型
   */
  private String eventType;

  /**
   * 事件实体
   */
  private byte[] eventBody;

  /**
   * 事件版本
   */
  private Integer eventVersion;

  private String sender;

  private Long senderId;

  private Date storedDate;

}

//事件持久化仓储接口
public interface IEventStore {
  void appand(DomainEvent event);
  .....
 }

IMediator接口是实现事件存储和事件发布中间协调者。

点击查看代码 //事件存储和发布接口 public interface IMediator { void publish(DomainEvent event); void saveAndPublish(DomainEvent event); } public class DefaultMediator implements IMediator { @autowired private final IEventStore eventStore;//事件存储仓库接口 @autowired private IRocketMQNotificationTemplate rocketMQNotificationTemplate;//RocketMQ实现 @Override public void saveAndPublish(DomainEvent event) { notificationRepository.appand(domainEvent);//事件追加 publishNotification(domainEvent);//事件发布 } @Override public void publish(DomainEvent event) { publishNotification(event); } private void publishNotification(DomainEvent event) { try{ SendResult sr = rocketMQNotificationTemplate.syncSendOrderly(event, String.format("%s-%s", event.getTagName(), event.getPersistenceId()), event.getEventId()); if(sr.getSendStatus().equals(SendStatus.SEND_OK)){ //事件状态更新 eventStore.updateEventStateComplete(Lists.newArrayList(event.getNotificationId())); } }catch (Exception e){ throw new RetryException("消息发送失败!注意重试幂等处理!eventId="+event.getEventId()); } } } ```

事件监听

AbstractRocketMQNotificationListener 抽象RocketMQ类及商品领域的实现监听类ProductNotificationListener

点击查看代码
/** 自定义注解为了路由指定主题消费,后续需要可以再加tag更细区分*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Service
public @interface MQConsumeService {
    /**
     * 消息主题
     */
     TopicEnum topic();
}

/** * 消费者消费消息路由处理**/
@Component
public class MQConsumeMsgListenerProcessor implements MessageListenerConcurrently{
      ....
   public void customeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context){
      Map<String, List<MessageExt>> topicGroups = msgs.stream().collect(Collectors.groupingBy(MessageExt::getTopic));
            for (Entry<String, List<MessageExt>> topicEntry : topicGroups.entrySet()) {
                   String topic = topicEntry.getKey();
                  Map<String, List<MessageExt>> tagGroups = topicEntry.getValue().stream().collect(Collectors.groupingBy(MessageExt::getTags));
                  for (Entry<String, List<MessageExt>> tagEntry : tagGroups.entrySet()) {
                    String tag = tagEntry.getKey();
                    this.consumeMsgForTopic(topic,tag,tagEntry.getValue());
                 }
            }
     }
   private  MQNotificationListener consumeMsgForTopic(String topic,String tag,List<MessageExt> value){
            MQNotificationListener mQNotificationListener = this.selectConsumeService(topic);
            //执行对应的handlle
            mQNotificationListener.handle(String topic, String tag, List<MessageExt> value);
     }
      ....
    //通过注解反射获取topic对应的消费者服务
   private MQNotificationListener selectConsumeService(String topic) {
        MQNotificationListener mQNotificationListener = null;
        for (Entry<String, MQNotificationListener> entry : mQNotificationListener.entrySet()) {
            //获取service实现类上注解的topic
            MQConsumeService consumeService = entry.getValue().getClass().getAnnotation(MQConsumeService.class);
            if(consumeService == null){
                logger.error("消费者服务:"+entry.getValue().getClass().getName()+"上没有添加MQConsumeService注解");
                continue;
            }
            String annotationTopic = consumeService.topic().getCode();
            if(!annotationTopic.equals(topic)){
                continue;
            }
            String[] tagsArr = consumeService.tags();
            boolean isContains = Arrays.asList(tagsArr).contains(tag);
            if(isContains){
                //获取该实例
                mQNotificationListener = entry.getValue();
                break;
            }
        }
        return mQNotificationListener;
    }

}
public interface MQNotificationListener {
    /**
     * @param topic 消息主题
     * @param tag 消息标签
     * @param msgs 消息
     * @return
     */
    MQConsumeResult handle(String topic, String tag, List<MessageExt> msgs);
}
/**模板设计模式实现消息的监听*/
public abstract class AbstractRocketMQNotificationListener implements MQNotificationListener{
    protected static final Logger logger = LoggerFactory.getLogger(AbstractRocketMQNotificationListener.class);
    @Override
    public MQConsumeResult handle(String topic, String tag, List<MessageExt> msgs) {
        MQConsumeResult mqConsumeResult = new MQConsumeResult();
        /**可以增加一些其他逻辑*/
        for (MessageExt messageExt : msgs) {
            //消费具体的消息,抛出钩子供真正消费该消息的服务调用
            mqConsumeResult = this.consumeMessage(tag,messageExt.getKeys()==null?null:Arrays.asList(messageExt.getKeys().split(MessageConst.KEY_SEPARATOR)),messageExt);
        }
        /**可以增加一些其他逻辑*/
        return mqConsumeResult;
    }
    /**
     * 消息某条消息
     * @param tag 标签
     * @param keys 消息关键字
     * @param messageExt
     * @return
     */
    protected abstract MQConsumeResult consumeMessage(String tag,List<String> keys, MessageExt messageExt);

}
/**商品主题消费处理*/
@MQConsumeService(topic=TopicEnum.ProductTopic)
public class ProductNotificationListener extends AbstractRocketMQNotificationListener{
    @Override
    protected MQConsumeResult consumeMessage(String tag, List<String> keys, MessageExt messageExt) {
        String msg = new String(messageExt.getBody());
        logger.info("获取到的消息为:"+msg);
        if(tag.equals("某个tagName")){//比如productTaxUpdate
            //做某个操作
        }
        //TODO 获取该消息重试次数
        int reconsume = messageExt.getReconsumeTimes();
        //根据消息重试次数判断是否需要继续消费
        if(reconsume ==3){//消息已经重试了3次,如果不需要再次消费,则返回成功
        }
        MQConsumeResult result = new MQConsumeResult();
        result.setSuccess(true);
        return result;
    }
}

posted @ 2023-03-21 11:44  曹化金  阅读(104)  评论(0编辑  收藏  举报