用pub / sub实现DDD中的领域事件

领域事件是DDD里的一个概念。牛逼的领域专家觉得:

在所有领域中,总是有相同的情况重复出现:领域中发生了某件事,需要对这件事做一些后续的操作,或者广播通知。例如,用户在完成注册后,系统会发出一封带有确认信息的邮件到用户的邮箱;用户关注的好友发送动态后他会收到相应的通知等等。

把这种经常出现的情况抽象为了领域事件。适当的抽象和建模领域事件,可以降低代码耦合,使代码更能反映领域模型的本质,原因是:

  • 这些后续操作基本都是:发送消息,处理数据同步,它们无关领域逻辑。把这些后续操作从主流程的逻辑中分离出来,让service / domain model只反映领域逻辑。
  • 隐藏技术细节:mq,redis等的处理细节
  • 易于维护:要新增,减小对领域事件的响应,只需要订阅 / 取消订阅一个register。

实现

接下来提供一个简单的实现:

实现起来一共有几个概念:

  • domainEvent,事件本身,比如UserLoginedEvent,就是一个特定的事件。
  • DomainEventPublisher,单例。提供发布/订阅功能。
  • Subscriber,订阅事件的handler。

代码非常简单:

DomainEvent:

/**
 * 领域事件
 */
public interface DomainEvent {
    /**
     * 领域事件发生的时间
     * @return
     */
    public LocalDateTime occurredOn();

}

Subscriber:

/**
 * 处理领域事件的subscriber
 * @param <T>
 */
public interface Subscriber<T extends DomainEvent> {
    /**
     * 处理领域事件
     * @param event
     * @return 处理结果,为false时不往下执行
     */
    public boolean handle(T event);
}

DomainEventPublisher:

@NotThreadSafe
public class DomainEventPublisher {

    private final static DomainEventPublisher INSTANCE = new DomainEventPublisher();

    private Map<Class<? extends DomainEvent>, List<Subscriber>> subscriberMap;

    private DomainEventPublisher() {
        subscriberMap = new ConcurrentHashMap<>();
    }

    public static DomainEventPublisher instance() {
        return INSTANCE;
    }

    public <T extends DomainEvent> void publish(T event) {
        Validator.checkNotNull(event);

        Class<?> klass = event.getClass();

        final List<Subscriber> subscribers = subscriberMap.get(klass);

        if (subscribers == null || subscribers.isEmpty()) {
            return;
        }

        for (Subscriber s : subscribers) {
            if (!s.handle(event)) {
                break;
            }
        }
    }

    public void register(Class<? extends DomainEvent> eventKlass, Subscriber subscriber) {
        if (eventKlass == DomainEvent.class) {
            throw new IllegalArgumentException("cannot register abstract DomainEvent");
        }

        List<Subscriber> subscribers = subscriberMap.get(eventKlass);

        if (subscribers == null) {
            synchronized (this) {
                if (subscribers == null) {
                    subscribers = new CopyOnWriteArrayList<>();
                    subscriberMap.put(eventKlass, subscribers);
                }
            }

            subscribers = subscriberMap.get(eventKlass);
        }

        subscribers.add(subscriber);
    }
}

参考

  • 《领域驱动设计》
  • 《实现领域驱动设计》
  • https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/domain-events-design-implementation
  • http://michael-j.net/2016/01/19/%E5%AE%9E%E7%8E%B0%E9%A2%86%E5%9F%9F%E4%BA%8B%E4%BB%B6/

Tags: ,

Updated: