基于事件驱动的DDD领域驱动设计框架分享(附源代码)

补充:现在再回过头来看这篇文章,感觉当初自己偏激了,呵呵。不过没有以前的我,怎么会有现在的我和现在的enode框架呢?发现自己进步了真好!

从去年10月份开始,学了几个月的领域驱动设计(Domain Driven Design,简称DDD)。主要是学习领域驱动设计之父Eric Evans的名著:《Domain-driven design:领域驱动设计:软件核心复杂性应对之道》,以及另外一本Martin Flower的《企业应用架构模式》,学习到了不少关于如何组织业务逻辑方面的知识。另外,在这个过程中也接触到了一些开源的架构和一些很好的思想。如:命令查询职责分离(Command Query Responsibility Segregation,简称CQRS),事件驱动架构(Event Driven Architecture,简称EDA),以及四色原型和DCI架构,等等。前面这些知识对我来说是非常宝贵的财富,可以说我能进淘宝,很大程度上也是因为我学习了前面这些知识的原因。

在介绍我设计的框架之前,我想先探讨一下以往我们都是如何思考设计OO的系统的。大家都知道,真正的对象应该是不仅有属性,而且有行为的。并且大家也有另外一个共识,那就是为了完成某个任务,各个对象应该会相互协作共同完成这个任务。之前我们在设计一个系统时,往往会先设计好各个对象,明确他们的职责,在这个过程中,还会考虑如何建立对象之间的关系(依赖、关联、聚合、组合),在这些关系的影响下,我们会认为对象之间应该有主从关系、依赖关系,等等。然后我们所做的这些设计最终的目的是为了能让对象之间能够通过相互协作来共同完成某个任务。这种方式最核心的设计特色是,我们会通过”对象引用“的方式来实现对象之间的各种关系。这种方式很好,并且我们也已经完全习惯了从对象的职责以及它们之间的关系的角度去设计对象。但这仅仅体现了一个哲学思想,那就是“物体之间通过直接作用完成某个任务”。

 

我觉得任何两个对象之间的交互有两种形式:1)直接作用,即对象A引用一个对象B,然后A调用B提供的某个方法,以此来完成两个对象之间的协作;2)间接作用,对象A不引用对象B,仅仅包含了一个对象B的唯一标识,当它要和对象B协作时,会发送一个消息给对象B,然后对象B收到该消息后做出响应,从而实现两个对象之间的协作;不管是哪种方式,他们最终的效果是一致的,都可以实现两个对象之间的交互并最终完成某个任务。那么这两种方式各自的优缺点在哪里呢?我个人觉得对于对象引用的方式,其好处就是简单、直观、容易理解,很符合我们平时的设计习惯。但坏处是什么呢?我个人觉得这种方式是形成对象耦合的根本原因,对象A对对象B存在了紧密的耦合,也许你会说,在间接作用的方式下,对象A不也会保留一个对象B的唯一标识吗?没错,但要知道保留引用和保留唯一标识的耦合强度是不一样的。前者的耦合强度更大,因为持有另外一个对象的引用就意味着可以直接操作该对象,而仅仅持有另外一个对象的唯一标识则不行,必须先根据该唯一标识获取另外一个对象,然后再操作它。而对于发送接受消息的方式,它的好处和坏处是什么呢?其实正好和前者相反,即不简单、不直观、不容易理解,容易让大家觉得有过度设计的嫌疑,而好处则是能够将两个对象之间的耦合度降到最低。

 

好了,有了前面这些介绍之后,我想可以引出我所设计的这个框架的设计思想了。

 

既然在对象直接作用的思路下设计软件的各种原则、模式,以及各种最佳实践已经很多了,如SOLID五大设计原则、GRASP九大OO设计原则、Gof的23种设计模式、各种更大的模式如MVC、MVP、MVVM,等等。所以,我也就不用去费功夫去研究了,直接利用前辈的研究成果就行了。但我发现在对象间接作用的思路下设计软件的各种原则或框架似乎还不够多。当然也有很多大家都很熟悉了,比如Observer设计模式,按照这个设计模式设计出来的.NET框架中的事件和委托的机制,还有比如一些第三方的开源框架如事件总线,事件驱动架构,等等。

 

思考到这里,再结合自己最近不断学习DDD的背景下,我脑子里有了一个奇特的想法!那就是:是否可以搞一个事件驱动的领域模型实现框架从而可以让我们从消息和行为的角度去设计对象呢?

有了这个框架,我们可以:1)通过消息实现领域模型中各个领域对象之间的交互,或者说是通信及协作;2)通过消息实现领域模型和外界的交互,如领域模型的使用者和领域模型之间的交互,一般这个使用者是应用层;还有比如领域模型和数据持久层的交互。带着这个问题,我试图去寻找目前已有的框架来实现我的想法,但遗憾的是,我找不到,所以只能自己开发。想到这里,我其实挺担心的,因为我很有可能已经走火入魔了,因为我要走的设计道路很可能是个死胡同或不归路,或者说不是一条真正能很好的解决软件设计的路,不然我怎么会找不到这样的框架呢?但不管怎样,还是先试试再说吧!反正我的大脑放在那里不用也是浪费。就这样,带着这样的目标和思路,我开始一步步设计我的框架了。 

经过了三个月的设计、编码、测试、分享、讨论、重构的循环过程。到目前为止,总算初步实现了自己当初的目标,现在唯一差的就是在真正的实际项目中使用了。但幸好已经写了两个不同层次的Demo用来验证我的框架了。

http://files.cnblogs.com/netfocus/EventBasedDDDExample.rar

该Demo包含了框架的源代码和Demo文件,基于VS2010开发,因为需要用到.NET4.0中的一些特性,如逆变和协变。源代码打开后,EventBasedDDDExample.PresentationLayer是启动项目,直接F5就可以运行。该Demo为了重点突出领域模型的设计,特意采用内存作为数据持久层,去掉了应用层,并且用控制台应用程序作为UI层,这样就方便大家运行Demo。该项目包含了四个演示的例子,前面两个例子演示了如何利用我的框架实现特定的业务场景(一个是银行转账的例子,另一个是论坛中发帖发回复删除回复的例子)。具体功能参见源代码。

http://files.cnblogs.com/netfocus/ProductName.rar

该Demo是一个比较真实的项目,也是用VS2010开发。前身是我之前开发过的一个蜘蛛侠论坛,现在用我最新的框架来实现这个论坛。但由于时间有限,UI层还没开发好,但应用层、领域层、持久层已经开发好。因此大家在查看源代码时,不要去看UI层的设计,因为我还没开发好。而应该去看其他几层的设计!大家从我这个Demo中,可以看到如何将经典的领域驱动四层分层架构和我的框架集成。相信这对大家非常具有实用价值。然后关于项目的命名空间,我也要解释下。假设现在有一个公司要做一个项目,我觉得比较好的项目命名方式为:以CompanyName.ProductName作为前缀,基础类库命名为Common,产品中的某个子应用模块,则可以命名为CompanyName.ProductName.Modules.Forum,CompanyName.ProductName.Modules.Blog,等。然后每个模块还可以根据模块的分层设计分出不同的Project,比如论坛的应用层可以命名为:CompanyName.ProductName.Modules.Forum.ApplicationService,等。由于我做的只是一个展示架构的Demo,所以没有用具体的CompanyName,ProductName。我觉得在开发阶段我们可以不使用最后的名字,到了最后项目快完成时再做统一全局替换即可。

 

下面介绍一下我的框架的设计思想:

领域模型的组成元素:领域服务(Domain Service)+领域对象(Domain Object)+领域事件(Domain Event)+中央事件处理器(Event Processer)。

  1. 领域服务:这个元素和Evans提到的领域服务一致,主要目的也是用来完成单个领域对象不能完成的职责,如银行转账操作;
  2. 领域对象:这个元素和Evans提到的Entity很类似,也有一个唯一标识,但和Evans中的概念也有不同的地方。比如Evans中的Entity为了保持领域模型的完整性,有聚合的概念,即Aggregate。另外还有值对象的概念,即Value Object。但在我的设计中,领域模型中的所有的对象都是平等的,没有任何聚合或关联的概念,也没有值对象的概念。所有的领域对象都通过事件来进行交互协作,从而达到完成各种任务的目的。
  3. 领域事件:这个元素在整个领域模型中最重要,就好比是人体的血液或神经。它是领域模型内部各个领域对象之间通信以及领域模型和外部通信时传递的信息的载体。通过领域事件,我们可以“串”连任何两个领域对象,从而达到让他们相互协作的目的。所谓的串联就是,一个对象发出消息,另外一个对象接收消息并做出正确响应。值得一提的是,我这里提到的事件不仅仅是通知别人发生了什么,而是泛指所有可能的通信情况。比如告诉别人我要什么(我想干什么),告诉别人我将要做什么,等等。也就是说,事件有可能带有一定的目的性,即有可能会指定应该由哪个对象去响应该事件。也许在你看来这已经不是标准的事件了,因为标准的事件应该是不可能知道会由哪些人回去响应该事件的。没错,它就是一个不标准的事件。我前面已经提到了,我这里的事件指对象之间通信的载体。而通信的情况是非常多的,肯定不只局限于告诉别人我发生了什么,还有非常多其他的情况。最后还有一点需要特别指出的是,事件发出去并响应后,有可能会有响应结果。关于这个问题,一般有两个实现方式:利用事件的回调函数实现;让事件响应函数提供返回值,然后在事件完全响应完成后,从事件对象中取出可能的返回值。我认为这两种方式都可以,我的框架采用的是后者。
  4. 中央事件处理器:这个元素只做一件事情,那就是处理某个传进来的事件。怎么处理?就是根据当前事件获取所有可能的响应者,然后调用每个响应者的响应函数执行每个响应。

另外,领域模型与外界如何交互呢?还是通过上面所提到的事件,当外界需要领域模型做什么事情时,就发送一个在领域模型中已经定义好的事件,然后领域模型或其他人(比如持久层)就会响应该事件了。当领域模型发生了什么或想要外界提供什么数据时,也是通过发送事件,然后外界就会响应,从而为领域模型提供必要的支持,如持久化支持。通过上面的分析,似乎可以看出我们已经找到银弹了,即找到了一种单一的模式可以用来解决所有的对象交互与协作的问题了?应该不是这样,但我自己没发现不知道这种设计的问题出在哪里,所以非常期望大家能多给我些意见。还是那句话,我希望我们每个中国人都是一个不盲目相信权威并敢于怀疑权威并能积极去思考和将自己的思考转化为生产力的人,而不只是一个仅仅会使用外国人写出来的框架的人。

这篇文章说的全部是思想或思考心得,接下来我会具体分析我上面提到的两个Demo的具体设计。但我真的很希望大家能重视思想,重视自己的思考过程,并且要敢于去将自己的思想转化为具体的成果,如框架。我们来这个地球上走一趟,如果仅仅只是会用别人写出来的东西,那不是很可惜?但如果你根据自己的思想写出了几个能让别人用的东西出来,那不是非常好吗?那才是很有意义和价值的事情。

时间: 2024-08-03 20:02:19

基于事件驱动的DDD领域驱动设计框架分享(附源代码)的相关文章

浅谈我对DDD领域驱动设计的理解

从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品.所以,自然而然就想到要做一个普通电商系统,用于实现在线销售自己企业产品的目的. 再比如,我是一家互联网公司,公司有很多系统对外提供服务,面向很多客户端设备.但是最近由于各种原因,导致服务经常出故障.所以,我们希望通过各种措施提高服务的质量和稳定性.其中的一个措施就是希望能做一个灰度发布的平台,这个

DDD领域驱动设计:聚合、实体、值对象

关于具体需求,请看前面的博文:DDD领域驱动设计实践篇之如何提取模型,下面是具体的实体.聚合.值对象的代码,不想多说什么是实体.聚合等概念,相信理论的东西大家已经知晓了.本人对DDD表示好奇,没有在真正项目实践过,甚至也没有看过真正的DDD实践的项目源码,处于极度纠结状态,甚至无法自拔,所以告诫DDD爱好者们,如果要在项目里面实践DDD,除非你对实体建模和领域职责非常了解(很多时候会纠结一些逻辑放哪里好,属于设计问题)以及你的团队水平都比较高认同DDD,否则请慎重...勿喷! 代码在后,请先看D

DDD领域驱动设计基本理论知识总结

领域驱动设计之领域模型 加一个导航,关于如何设计聚合的详细思考,见这篇文章. 2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称Evans DDD.领域驱动设计分为两个阶段: 以一种领域专家.设计人员.开发人员都能理解的通用语言作为相互交流的工具,在交流的过程中发现领域概念,然后将这些概念设计成一个领域模型: 由领域模型驱动软件设计,用代码来实现该领域模型

关于DDD领域驱动设计的理论知识收集汇总

最近一直在学习领域驱动设计(DDD)的理论知识,从网上搜集了一些个人认为比较有价值的东西,贴出来和大家分享一下: 我一直觉得不要盲目相信权威,比如不能一谈起领域驱动设计,就一定认为国外的那个Eric Evans写的那本书中的一些概念就一定是正确的,认为领域驱动设计就一定是聚合,聚合根,实体,值对象等概念.我们要有自己的思想,要有自己判断真正的领域模型该是什么样子的勇气和追求. "领域驱动设计" = "问题域模型驱动领域建模" + "领域建模驱动软件实现&q

DDD领域驱动设计:领域服务

什么是领域服务,DDD书中是说,有些类或者方法,放实体A也不好,放实体B也不好,因为很可能会涉及多个实体或者聚合的交互(也可能是多个相同类型的实体),此时就应该吧这些代码放到领域服务中,领域服务其实就跟传统三层的BLL很相似,只有方法没有属性,也就没有状态,而且最好是用动词命名,service为后缀,但是真正到了实践的时候,很多时候是很难区分是领域实体本身实现还是用领域服务区实现的,除了那些需要操作(一般是参数了)多个实体的方法外,有些单个实体的操作是很难严格区分的,实际上放实体和领域服务都可以

DDD领域驱动设计:领域基础设施层

其实这里说的基础设施层只是领域层的一些接口和基类而已,没有其他的如日子工具等代码,仅仅是为了说明领域层的一些基础问题 1.领域事件简单实现代码,都是来至ASP.NET设计模式书中的代码 namespace DDD.Infrastructure.Domain.Events { public interface IDomainEvent { } } namespace DDD.Infrastructure.Domain.Events { public interface IDomainEventHa

DDD领域驱动设计 - 设计文档模板

设计文档模板: 系统背景和定位 业务需求描述 系统用例图 关键业务流程图 领域语言整理,主要是整理领域中的各种术语的定义,名词解释 领域划分(分析出子域.核心域.支撑域) 每个子域的领域模型设计(实体.值对象.聚合.领域事件,需要注意的是:领域模型是需要抽象的,要分析业务本质,而不是简单的直接对需求进行建模) 领域模型详细说明(如为什么这样设计的原因.模型内对象的关系.各种业务规则.数据一致性规则等) 领域服务.仓储.工厂设计 Saga流程设计 场景走查(讲述如何通过领域模型.领域服务.仓储.S

DDD领域驱动设计实践篇:如何提取模型

需求说明: 省级用户可以登记国家指标 省级用户和市级用户可以登记指标分解 登记国家指标时,需要录入以下数据:指标批次.文号.面积,这里省略其他数据,下同 登记指标分解时,需要录入以下数据:指标批次.文号.面积,以及可以选择多个市(市级登记的时候是县)的指标,每个市(县)的指标也是要输入批次.文号.面积 登记指标分解时,一个指标批次不能选择多个相同的市(县) 登记指标分解时,需要判断当前剩余面积是否足够,比如省登记的时候,要看国家本年度下发给省的指标面积是否大于省本年度所以指标面积,登记国家指标不

领域驱动设计-相关理论

最近 Rafy 开源中心 启动刚一个月,在初始的讨论会上,成员们对面向对象设计.领域驱动设计等概念展开了大量的讨论. 下面我转载一篇文章,这篇文章的详细内容我都还没看完.不过,文章的结构正是我想要的!其结构非常清晰,很好地说明了领域驱动设计相关的起源.重点.模式.经典架构,以及一些后人扩展的新概念. 转载自<DDD领域驱动设计基本理论知识总结>,并稍微调整了一下内容顺序. 为什么面向对象比面向过程更能适应业务变化 对象将需求用类一个个隔开,就像用储物箱把东西一个个封装起来一样,需求变了,分几种