`

Domain Events – 救世主

    博客分类:
  • DDD
阅读更多

原文 http://www.jdon.com/jivejdon/thread/37289/15

 

在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;}
}

很难想象,一个实体模型中的方法参数依赖服务或者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代码可以见最新的JiveJdon 3.7源码。

感谢Udi Dahan的博文,否则我不会有将我自己实现Domain Event经历和构思写出来,因为发现我这种解决思路可以为更多人提供参考。

有一个号称是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

 

>it is surrounded by adapters listening to events happening in the domain. Adapters handle these events by calling on external services.

非常棒,这里adapters listening实际就是Observer模式,监听者模式和观察者模式原理基本一样,可以理解为同一模式。

监 听者模式和事件模式是紧密联系的。通过监听模式引入,可以将领域模型和服务以前其他底层的一些操作进行松耦合,从另外一个角度来说,注射IOC模式对于解 决聚合性质的耦合比较擅长,对于非常活跃的事件模式,则GOF的行为模式中各个模式值得借鉴,其中包括Command模式和观察者模式。

由此也可以看出,用好DDD 的基础是GoF设计模式。

bastion这个开源DDD 框架虽然很简单,但是它对我的启发很大,特别是它将观察模式固化到Domain这个核心类中,用来辅助领域模型和外界的事件交互,通过Event和Message来实现模型和服务等外界交互,这个想法和我在Jdon框架中的异步 观察者模式有异曲同工之妙:

bastion的Domain中事件触发方法:

protected

 <T extends

 DomainMessage> T notifyInternal(T message) {
        List<Adapter> messageAdapters = adapters.get(message.getClass());
        if

 (messageAdapters != null

) {
        	for

 (Adapter<? super

 DomainMessage> adapter : messageAdapters) {
				adapter.handle(message); //激活每个监听者的handle方法




			}
        }
        return

 message;
 }



JiveJdon中ForumThread的事件触发方法:
private

 void

 notifyObservers(Subscribed subscribed) {
		if

 (subscriptionObservable != null

) {
			Object[] args = new

 Object[] { subscribed };
			subscriptionObservable.notifyObservers(args); //激活每个观察者




		}
}



两者区别之处是:bastion将之鲜明整入Domain这个核心类中,而Jdon框架则没有如此显式和Domain挂钩,看来Jdon框架可以跨出这一步,因为这个Domain Event是非常重要的普遍的一个DDD 中解决方案。

 

>何时注册那些监听器?ACTION生成的时候?
bastion中是在threadlocal中开始注册的,也就是一个请求开始时,就是当前这个实体对象被创建时,将监听器注册到其中,因为一般实体对象都一直活着,在缓存 内存中,因此,这个实体对象以后还是可以继续加入新的监听者的。

我准备在Jdon框架中让监听器注册由框架自动完成,而不是现在由应用者自己完成,这样,会更方便。

 

如果框架能解决那最好,现有SPRING能实现自动注册吗?多个实体能共有一个注册吗?

 

其实可以在Jdon框架或Spring框架中直接使用bastion框架,bastion框架主要是让除了领域模型以外的组件模型成为其卫星,就象太阳是核心,其他都绕着太阳转。

补充:DDD的事件顺序图如下:



[该贴被banq于2009-10-15 09:30修改过]


 

bastion框架集成SPRING,有例子吗?

 

>bastion框架集成SPRING
应该可以,Spring其实是和Domain Model无关的技术框架,以Domain Model观点看来,Domain Model就是与计算机概念无关的,而Spring属于那种和计算机有关的概念。

bastion框架是计算机概念和领域模型的结合部位,所以,两者能够使用。

我目前发现JavAte比Bastion更加全面,对DOmain Events处理也更加丰富,可见:
JavAte

 

bastion框架中的适配监听器的触发机存在这样的问题:
领 域消息事件与适配监听器是一对多的关系,按照现在的设计,同一个消息事件可能会触发多个适配监听器,不甚合理。例如,A、B模块需要在同一个查询消息事件 下绑定各自的查询适配监听器,A模块的发送的查询消息事件同时会触发B模块的查询服务。是否需要为在同一消息事件下绑定的不同适配监听器设置标示符,从而 能够和领域消息事件中存储标示符匹配?

是的,我从Jdonframework 6.2开发中也感觉这个问题,原来设置观察者模式是一对多,结果发现使用变得复杂,现在改为一对一,简单。

 

分享到:
评论

相关推荐

    详解ABP框架中领域层的领域大事Domain events_.docx

    在ABP框架中,领域层的领域大事(Domain Events)是实现业务逻辑解耦和事件驱动设计的重要工具。本文将深入解析ABP框架中的Domain Events及其使用方法。 首先,理解领域大事的概念。在C#编程中,一个类可以定义自己...

    Spring Data的Domain Event的用法详解

    例如,在Person这个Aggregate Root中,可以使用@DomainEvents注解方法发布事件,如: ```java @Entity @Data @AllArgsConstructor @NoArgsConstructor @ToString(exclude = "domainEvents") public class Person { ...

    Cross Domain-CROS(跨域助手).zip

    Cross Domain will help you to deal with cross domain - CORS problem. This is tool helpful when face with cross domain issue.

    EventualConsistency.DomainEvents.AzureServiceBus:通过域事件和Azure Service Bus最终实现一致性

    本项目"EventualConsistency.DomainEvents.AzureServiceBus"是基于C#实现的一个框架,用于通过域事件(Domain Events)和Azure Service Bus来实现最终一致性。 域事件(Domain Events)是领域驱动设计(Domain-...

    Domain-Driven Design –Tackling Complexity in the Heart of Software.pdf

    The software development community widely acknowledges that domain modeling is central to software design. Through domain modeling, software developers are able to express rich functionality and ...

    Powershell – domain user names in detail

    这些服务可能包括Active Directory Lightweight Domain Services、Active Directory Domain Services或Active Directory Snapshot实例。 在指定域名称时,可以使用完全限定域名,例如***,或者使用NetBIOS名称,如...

    Domain Decomposition Methods Algorithms and Theory

    标题“域分解方法——算法与理论”简明扼要地概述了本书的主题:域分解方法(Domain Decomposition Methods)。这种方法是一种数值求解偏微分方程的有效手段,特别适用于大规模并行计算环境中的复杂问题。它通过将...

    详解ABP框架中领域层的领域事件Domain events

    ABP是基于ASP.NET框架之上的Web开发框架(GitHub:https://github.com/aspnetboilerplate),这篇我们来详解ABP框架中领域层的领域事件Domain events,需要的朋友可以参考下

    详细介绍ApplicationDomain和SecurityDomain

    ### 详细介绍ApplicationDomain和SecurityDomain #### 一、概述 在深入探讨ApplicationDomain和SecurityDomain之前,我们首先明确这两个概念的基本定义及其在Flash环境中的重要性。ApplicationDomain和Security...

    深入理解ApplicationDomain和SecurityDomain

    ### 深入理解ApplicationDomain和SecurityDomain #### 安全域(Security Domain)与应用程序域(Application Domain)概述 安全域与应用程序域是Flash Player中两种重要的沙箱概念,它们帮助开发者理解如何管理和...

    Xen如何创建DomainU

    Xen 创建 DomainU 的流程分析 Xen 是一种开源的虚拟机监视器,它可以创建多个虚拟机(DomainU),每个虚拟机都是一个独立的操作系统环境。本文将分析 Xen 是如何创建 DomainU 的,这个过程是深入了解 Xen 的必备...

    Domain Generalization A Survey.pdf

    机器视觉和机器学习中的Domain Generalization研究综述 Domain Generalization(域泛化)是机器学习和机器视觉领域中一个热门的研究方向。该技术旨在解决机器学习模型在不同环境、场景或数据分布下无法泛化的问题。...

    Optimal Transport for Domain Adaptation

    Domain adaptation is one of the most challenging tasks of modern data analytics. If the adaptation is done correctly, models built on a specific data representation become more robust when confronted ...

    DataDomain-Storage viso

    标题 "DataDomain-Storage viso" 暗示我们正在讨论的是有关EMC DataDomain存储系统的可视化工具。DataDomain是EMC公司(现为戴尔科技集团的一部分)的一款数据保护和存储解决方案,专长在于重复数据删除技术,常用于...

    EMC_DATA_DOMAIN管理员手册

    ### EMC Data Domain 系统管理课程知识精要 #### 标题解读:EMC Data Domain 管理员手册 EMC Data Domain 是一款由EMC公司开发的高性能备份与恢复解决方案,专为数据去重(Deduplication)而设计,能够显著减少...

    领域驱动(DDD)充血模式下,domain 与 Service以及Repository的解耦---DOMAIN EVENT

    在软件开发领域,尤其是企业级应用中,领域驱动设计(Domain-Driven Design,简称DDD)是一种重要的设计思想。本文将详细探讨DDD中的“充血模式”(Bounded Contexts with Rich Domain Models),以及如何通过引入...

    EMC DataDomain操作系统初始配置指南.pdf

    "EMC DataDomain操作系统初始配置指南" titre:EMC DataDomain操作系统初始配置指南 本指南旨在指导用户完成EMC DataDomain操作系统的初始配置。该操作系统版本为6.0,发布日期为2016年10月。 描述: EMC Data...

    ip domain-lookup命令解释

    ### ip domain-lookup命令详解 在现代网络管理与配置中,`ip domain-lookup` 命令扮演着至关重要的角色。此命令主要用于在Cisco设备上启用或禁用域名解析功能,即DNS(Domain Name System)查询。通过该命令,网络...

    博科光纤交换机修改DOMAIN ID

    在配置这些交换机时,一个关键的设置是DOMAIN ID,它在FC环境中用作身份识别,确保网络中的各个设备能够正确通信。这篇内容将详细讲解如何在博科光纤交换机上修改DOMAIN ID,这对于初学者尤其有帮助。 首先,了解...

    domain-events:简单的域事件实现

    use \ Fesor \ DomainEvent \ DomainEvents ; class User { use DomainEvents ; private $ email ; private $ password ; public function __constructor ( Email $ email , Password $ password ) { ...

Global site tag (gtag.js) - Google Analytics