Domain Events – 救世主
在Evans DDD实现过程中,经常会碰到实体和服务Service以及Repository交互过程,这个交互过程的实现是一个难点,也是容易造成失血贫血模型的主要途径。
因为实体的业务方法需要和服务或Reposirtoy打交道,如果把这个业务方法放入服务,就容易造成实体的贫血;但是如果把服务注射到实体中,也非常丑陋。这里提出一个中间处理模式:Domain Event,领域事件模式,这个模式也曾经被MF在文章Domain Event专门章节提到。
2008年Udi Dahan在其博客How to create fully encapsulated Domain Models一文中也提出这个问题,引起大家重视。
Udi Dahan的案例是游戏购物车:对于商品放入购物车有三个规则:
1. 只有三个游戏才能加入购物车
2. 购物车中的总数不能超过10.
3. 如果该客户报失丢失了自己的租金会员,没有游戏可以被添加
前面两个规则可以在实体模型中容易实现,但是第三个条件需要和服务打交道了。
|
很难想象,一个实体模型中的方法参数依赖服务或者Repository?作者向大家寻求一个统一的模式来解决此一类问题。
经过近一年的讨论,2009年6月14日作者在征询很多意见后,再次在其博客Domain Events – Salvation
提出了Domain Event的解决方案,并且声称:
不要把任何东西注射到你的领域实体中,没有服务,没有仓储:
The main assertion being that you do *not* need to inject anything into your domain entities.
Not services. Not repositories. Nothing.
并且给出了Domain Event的具体实现,总体来说:就是在上面购物车实体和LoggingService服务之间引入一个事件消息模型Domain Event。关于消息事件模型我在EDA: Event-Driven Architecture事件驱动架构已经阐述:事件和消息可以说是从不同方面描述的同一个东西,消息是事件发生后产物,消息发送必须有发送事件发生才能实现。每次事件只发送一次消息,事件和消息是一对一的。
Udi Dahan的DomainEvents类底层实现实际是一个事件模式实现,采取Command模式同步机制实现的,有兴趣这可以翻墙过去看看源码,这里我提出我自己在JiveJdon主题订阅功能实现中提出的异步Domain Events模式。
JiveJdon主题订阅功能需求是这样:当用户对某个主题感兴趣,希望这个主题贴比如当前这个有新回复时通知它,他就可以使用主题订阅关注这个主题。
那么实体模型ForumThread就变成被订阅者,而用户就成为订阅者,这样,当ForumThread的业务方法addNewMessage(新回复)被调用时,立即通知订阅者。
在这个实现中,ForumThread中addNewMessage方法中增加一个通知订阅者方法就可以了,但是这个ForumThread有哪些订阅者,不可能通过聚合关系一直将ForumThread的订阅者都一次性纳入其中,这个问题在gamex帖子http://www.jdon.com/jivejdon/thread/37288
中已经提及,我们当然是采取查询的方式,但是查询就涉及数据库里哦啊,是否在ForumThread中addNewMessage方法引入Repository?
所以,我也碰到了和Udi Dahan当初一样的问题,我之前没有看过他的这篇文章,是因为刚才看到DDD: Entity Injection and Mocking Time文章才找到Udi Dahan的Doman Event模式,因为我对这个标题Entity Injection实体注射感兴趣,因为我在Jdon框架实践中,有时感觉Jdon框架不能支持实体注射(只有服务注射)而不便,也在考虑是否需要实体注射,当然,我现在同意Udi Dahan意见,不要将任何东西注射到实体中,这样的危险就是导致实体不是主体,而成为一个被动体,成为被动体的危险就是容易导致贫血模型。
而Domain Event模式可以让实体成为事件的发生源,成为主体。
我在Jdon框架6.1版本中引入了异步观察者模式,是这样考虑的:在众多实体模型关系中,分两大类:第一类是紧密关联,也就是以聚合关系存在的,这些对象们以DDD中聚合边界为范围紧密团结在一起,如JiveJdon的ForumThread和ForumMessage们,第二种:还有一些关系并不如聚合关联那么紧密,但是和核心模型有关联,是一种非常松散的关联,如何实现他们之间变动事件的传递?
这种事件消息传递有两种方式:同步和异步,Udi Dahan博客中提出的是Command性质的同步,而我提出引入异步观察者模式来实现Domain Event的异步,底层实现主要是借助Jdk 6.0的并发Conncurrent模型实现了异步事件处理功能,见Jdon框架源码:com.jdon.async.EventProcessor。
异步观察者模式步骤和JDK提供的同步观察者模式肥差类似:
1. 继承TaskObserver,实现其action方法,这是激活后所要实现的方法。
2. 将观察者TasjObserver加入ObservableAdapter。
3. 将设置观察点,在被观察或监听的类中,调用ObservableAdapter,在具体激活方法中调用ObservableAdapter的notifyObservers方法。
回到JiveJdon的主题订阅实现中,这样,我在ForumThread中引入一个对象ObservableAdapter被观察点,在ForumThread的仓储构造ThreadDirector,创建ForumThread时,为其注入new ObservableAdapter(com.jdon.async.EventProcessor.eventProcessor),这个EventProcessor相当于Udi Dahan
的Domain Event底层实现(可见其博客上源码)。
这样,在ForumThread中addNewMessage方法中增加subscriptionObservable.notifyObservers(args),这是激活观察者的一个事件或消息,由此观察者com.jdon.jivejdon.model.subscription.SubUpdateObserver的action方法就被异步激活,也就是说,这个action的执行是不妨碍主程序addNewMessage方法的执行,两者是两个线程同步并行实现的,这也是异步的好处,可以充分利用多CPU好处。action方法是查找数据库中该主题订阅的用户,这个过程
是可能缓慢的,因为和addNewMessage分开执行,因此,用户回复一个主题时调用addNewMessage,并不会因为缓慢的action方法而拖慢响应,用户回复主题的性能和速度是快速的,这里也体现良好DDD设计是高性能的一个保障。
这个使用异步观察者实现的Domain Event代码可以见最新的JiveJdon3.7源码。
感谢Udi Dahan的博文,否则我不会有将我自己实现Domain Event经历和构思写出来,因为发现我这种解决思路可以为更多人提供参考。
[该贴被admin于2009-10-12 12:02修改过]