Domain Events – 救世主

09-10-11 banq
                   

在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. 如果该客户报失丢失了自己的租金会员,没有游戏可以被添加

前面两个规则可以在实体模型中容易实现,但是第三个条件需要和服务打交道了。

class TradeInCart{
      Account Account{get;}
      LineItem Add(  
          Game game, 
          IRepository<QueueHistory> repository, 
          LoggingService service);
  
     ValidationResult CanAdd(
            Game game, 
            IRepository<QueueHistory> repository, 
            LoggingService service);
  
        IList<LineItems> LineItems{get;}
}
<p>

很难想象,一个实体模型中的方法参数依赖服务或者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修改过]

                   

13
oojdon
2009-10-11 19:54

谢谢banq

问一下,你是怎样找到这些文章的?呵呵

r7raul
2009-10-11 20:46

哈哈,这问题也困挠着我....终于有救世主了

[该贴被r7raul于2009-10-11 20:47修改过]

bosslee
2009-10-11 23:49

豁然开朗。 多谢

oojdon
2009-10-12 09:33

有一个号称是DDD的框架:bastion,其作者的博客有些内容比较有意思,注意加粗部分

A domain should be ever-expanding, it knows no boundaries. Changes in requirements should lead to additions, not changes to the domain.

A domain should always be live, always active.

A domain should be able to execute business processes dynamically. As business processes change continually, a dynamic domain model is a better way to support them than a process model, which is static.

A domain should have no infrastructural dependencies (e.g. persistence, authentication, logging). Instead, it is surrounded by adapters listening to events happening in the domain. Adapters handle these events by calling on external services. As these services are independent of the domain, they can be made generic and therefore reusable between domains.

A domain should be modelled using the time-inversion pattern and the active-passive pattern. That is: start modelling behaviour at the end of the process (in the spirit of demand chain management), and model passive objects in the real world as active ones in the domain model.

原文地址:http://www.blog.dannynet.net/archives/125

[该贴被oojdon于2009-10-12 09:34修改过]

5Go 1 2 3 4 ... 5 下一页