基于“事件”驱动的领域驱动设计(DDD)框架分析
摘抄自
从去年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)。
- 领域服务:这个元素和Evans提到的领域服务一致,主要目的也是用来完成单个领域对象不能完成的职责,如银行转账操作;
- 领域对象:这个元素和Evans提到的Entity很类似,也有一个唯一标识,但和Evans中的概念也有不同的地方。比如Evans中的Entity为了保持领域模型的完整性,有聚合的概念,即Aggregate。另外还有值对象的概念,即Value Object。但在我的设计中,领域模型中的所有的对象都是平等的,没有任何聚合或关联的概念,也没有值对象的概念。所有的领域对象都通过事件来进行交互协作,从而达到完成各种任务的目的。
- 领域事件:这个元素在整个领域模型中最重要,就好比是人体的血液或神经。它是领域模型内部各个领域对象之间通信以及领域模型和外部通信时传递的信息的载体。通过领域事件,我们可以“串”连任何两个领域对象,从而达到让他们相互协作的目的。所谓的串联就是,一个对象发出消息,另外一个对象接收消息并做出正确响应。值得一提的是,我这里提到的事件不仅仅是通知别人发生了什么,而是泛指所有可能的通信情况。比如告诉别人我要什么(我想干什么),告诉别人我将要做什么,等等。也就是说,事件有可能带有一定的目的性,即有可能会指定应该由哪个对象去响应该事件。也许在你看来这已经不是标准的事件了,因为标准的事件应该是不可能知道会由哪些人回去响应该事件的。没错,它就是一个不标准的事件。我前面已经提到了,我这里的事件指对象之间通信的载体。而通信的情况是非常多的,肯定不只局限于告诉别人我发生了什么,还有非常多其他的情况。最后还有一点需要特别指出的是,事件发出去并响应后,有可能会有响应结果。关于这个问题,一般有两个实现方式:利用事件的回调函数实现;让事件响应函数提供返回值,然后在事件完全响应完成后,从事件对象中取出可能的返回值。我认为这两种方式都可以,我的框架采用的是后者。
- 中央事件处理器:这个元素只做一件事情,那就是处理某个传进来的事件。怎么处理?就是根据当前事件获取所有可能的响应者,然后调用每个响应者的响应函数执行每个响应。
另外,领域模型与外界如何交互呢?还是通过上面所提到的事件,当外界需要领域模型做什么事情时,就发送一个在领域模型中已经定义好的事件,然后领域模型或其他人(比如持久层)就会响应该事件了。当领域模型发生了什么或想要外界提供什么数据时,也是通过发送事件,然后外界就会响应,从而为领域模型提供必要的支持,如持久化支持。通过上面的分析,似乎可以看出我们已经找到银弹了,即找到了一种单一的模式可以用来解决所有的对象交互与协作的问题了?应该不是这样,但我自己没发现不知道这种设计的问题出在哪里,所以非常期望大家能多给我些意见。还是那句话,我希望我们每个中国人都是一个不盲目相信权威并敢于怀疑权威并能积极去思考和将自己的思考转化为生产力的人,而不只是一个仅仅会使用外国人写出来的框架的人。
这篇文章说的全部是思想或思考心得,接下来我会具体分析我上面提到的两个Demo的具体设计。但我真的很希望大家能重视思想,重视自己的思考过程,并且要敢于去将自己的思想转化为具体的成果,如框架。我们来这个地球上走一趟,如果仅仅只是会用别人写出来的东西,那不是很可惜?但如果你根据自己的思想写出了几个能让别人用的东西出来,那不是非常好吗?那才是很有意义和价值的事情。
基于“事件”驱动的领域驱动设计(DDD)框架分析的更多相关文章
- 基于Mongodb的轻量级领域驱动框架(序)
混园子也有些年头了,从各个大牛那儿学了很多东西.技术这东西和中国的料理一样,其中技巧和经验,代代相传(这不是舌尖上的中国广告).转身回头一望,几年来自己也积累了一些东西,五花八门涉猎到各种方向,今日开 ...
- 基于ABP落地领域驱动设计-00.目录和小结
<实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...
- DDD(Domain-Driven Design) 领域驱动设计
DDD(Domain-Driven Design) 领域驱动设计 1. DDD(Domain-Driven Design)是什么? DDD是Eric Evans在2003年出版的<领域驱动设计: ...
- (转)EntityFramework之领域驱动设计实践
EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领 ...
- EntityFramework之领域驱动设计实践
EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领 ...
- 什么是领域驱动设计(Domain Driven Design)?
本文是从 What is Domain Driven Design? 这篇文章翻译而来. ”…在很多领域,专家的作用体现在他们的专业知识上而不是智力上.“ -- Don Reinertsen 领域驱动 ...
- DDD领域驱动之干活(四)补充篇!
距离上一篇DDD系列完结已经过了很长一段时间,项目也搁置了一段时间,想想还是继续完善下去. DDD领域驱动之干货(三)完结篇! 上一篇说到了如何实现uow配合Repository在autofac和au ...
- DDD领域驱动之干货(四)补充篇!
距离上一篇DDD系列完结已经过了很长一段时间,项目也搁置了一段时间,想想还是继续完善下去. DDD领域驱动之干货(三)完结篇! 上一篇说到了如何实现uow配合Repository在autofac和au ...
- C#中的异步调用及异步设计模式(三)——基于事件的异步模式
四.基于事件的异步模式(设计层面) 基于事件的C#异步编程模式是比IAsyncResult模式更高级的一种异步编程模式,也被用在更多的场合.该异步模式具有以下优点: · ...
随机推荐
- flask安装及第一个程序
1.flask是一个轻量级的python web框架 ·1.Flask 依赖两个外部库: Jinja2 模板引擎和 Werkzeug WSGI 套件 ·2.安装: # easy_install fla ...
- linux win 通用的获取Mac的方法
经测试下面方法获取Mac跨平台 protected override void OnLoad(EventArgs e) { Response.Write(string.Join("<b ...
- getAttribute和getParameter的区别
2016年1月19日JSP中getParameter与getAttribute有何区别? ——getParameter得到的都是String类型的.或者是http://a.jsp?id=123中的12 ...
- vim 配置,我本机的配置[windows]
set nocompatible source $VIMRUNTIME/vimrc_example.vim source $VIMRUNTIME/mswin.vim behave mswin set ...
- Subversion命令汇总
转自:http://www.cnblogs.com/cnblogsfans/archive/2010/03/21/1690838.html svn 命令共同的选项 --targets list 读取l ...
- Linux网络参数设置
1.ifconfig 查询.设定网络卡与ip 设置桥接网络 # vi /etc/sysconfig/network-script/ifcfg-br0 DEVICE=br0 ...
- codevs4096 删数问题
题目描述 Description 键盘输入一个高精度的正整数N,去掉其中任意S个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的N 和S,寻找一种方案使得剩下的数字组成的新数最小. 输入 ...
- java httpclient发送json 请求 ,go服务端接收
/***java客户端发送http请求*/package com.xx.httptest; /** * Created by yq on 16/6/27. */ import java.io.IOEx ...
- centos7 静态ip设置
TYPE=Ethernet BOOTPROTO=static DEFROUTE=yes IPV4_FAILURE_FATAL=no IPV6INIT=yes IPV6_AUTOCONF=yes IPV ...
- 【转】WordPress转PHPCMS策略-数据库完美转换
来源:http://www.sjyhome.com/php/wp-to-pc-sql.html WordPress的访问速度不可恭维?那就试试能够生成纯静态的PHPCMS,保证能够让你的网页访问速度有 ...