领域驱动设计和实践-转载

标签: 转载文章 | 发表时间:2012-04-07 20:22 | 作者:人月神话
出处:http://blog.sina.com.cn/cmmi
引言(原文: http://www.kuqin.com/system-analysis/20110912/264696.html)

软件系统面向对象的设计思想可谓历史悠久,20世纪70年代的Smalltalk可以说是面向对象语言的经典,直到今天我们依然将这门语言视为面向对象语言的基础。随着编程语言和技术的发展,各种语言特性层出不穷,面向对象是大部分语言的一个基本特性,像C++、Java、C#这样的静态语言,Ruby、Python这样的动态语言都是面向对象的语言。

但是面向对象语言并不是银弹,如果开发人员认为使用面向对象语言写出来的程度本身就是面向对象的,那就大错特错了,实际开发中,大量的业务逻辑堆积在一个巨型类中的例子屡见不鲜,代码的复用性和扩展性无法得到保证。为了解决这样的问题,领域驱动设计提出了清晰的分层架构和领域对象的概念,让面向对象的分析和设计进入了一个新的阶段,对企业级软件开发起到了巨大的推动作用。

本文主要介绍了领域驱动设计的基本概念、要素、特点,对比了事务脚本和领域模型的特点,最后介绍了我们在软件开发过程中的领域驱动设计实践。

什么是领域驱动设计(DDD)

2004年著名建模专家Eric Evans发表了他最具影响力的书籍:《Domain-Driven Design –Tackling Complexity in the Heart of Software》(中文译名:领域驱动设计—软件核心复杂性应对之道),书中提出了“领域驱动设计(简称 DDD)”的概念。

领域驱动设计事实上是针对OOAD的一个扩展和延伸,DDD基于面向对象分析与设计技术,对技术架构进行了分层规划,同时对每个类进行了策略和类型的划分。

领域模型是领域驱动的核心。采用DDD的设计思想,业务逻辑不再集中在几个大型的类上,而是由大量相对小的领域对象(类)组成,这些类具备自己的状态和行为,每个类是相对完整的独立体,并与现实领域的业务对象映射。领域模型就是由这样许多的细粒度的类组成。基于领域驱动的设计,保证了系统的可维护性、扩展性和复用性,在处理复杂业务逻辑方面有着先天的优势。

领域驱动设计的特点

领域驱动的核心应用场景就是解决复杂业务的设计问题,其特点与这一核心主题息息相关:

  • 分层架构与职责划分:领域驱动设计很好的遵循了关注点分离的原则,提出了成熟、清晰的分层架构。同时对领域对象进行了明确的策略和职责划分,让领域对象和现实世界中的业务形成良好的映射关系,为领域专家与开发人员搭建了沟通的桥梁。
  • 复用:在领域驱动设计中,领域对象是核心,每个领域对象都是一个相对完整的内聚的业务对象描述,所以可以形成直接的复用。同时设计过程是基于领域对象而不是基于数据库的Schema,所以整个设计也是可以复用的。
  • 使用场景:适合具备复杂业务逻辑的软件系统,对软件的可维护性和扩展性要求比较高。不适用简单的增删改查业务。

如果不使用DDD?

面对复杂的业务场景和需求,如果没有建立和实现领域模型,会导致应用架构出现“胖服务层”和“贫血的领域模型”,在这样的架构中,Service层开始积聚越来越多的业务逻辑,领域对象则成为只有getter和setter方法的数据载体。这种做法还会导致领域特定业务逻辑和规则散布于多个的Service类中,有些情况下还会出现重复的逻辑。我们曾经见过5000多行的Service类,上百个方法,代码基本上是不可读的。

在大多数情况下,贫血的领域模型没有成本效益。它们不会给公司带来超越其它公司的竞争优势,因为在这种架构里要实现业务需求变更,开发并部署到生产环境中去要花费太长的时间。

领域驱动设计的分层架构和构成要素

下面我们简单介绍一下领域驱动设计的分层架构和构成要素,这部分内容在Eric Evans的书中有非常详尽的描述,想要详细了解的,最好去读原版书籍。

下面这张图是该书中著名的分层架构图,如下:




整个架构分为四层,其核心就是领域层(Domain),所有的业务逻辑应该在领域层实现,具体描述如下:

  • 用户界面/展现层:负责向用户展现信息以及解释用户命令。
  • 应用层:很薄的一层,用来协调应用的活动。它不包含业务逻辑。它不保留业务对象的状态,但它保有应用任务的进度状态。
  • 领域层:本层包含关于领域的信息。这是业务软件的核心所在。在这里保留业务对象的状态,对业务对象和它们状态的持久化被委托给了基础设施层。
  • 基础设施层:本层作为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化,包含对用户界面层的支撑库等作用。

领域驱动设计除了对系统架构进行了分层描述,还对对象(Object)做了明确的职责和策略划分:

  • 实体(Entities):具备唯一ID,能够被持久化,具备业务逻辑,对应现实世界业务对象。
  • 值对象(Value objects):不具有唯一ID,由对象的属性描述,一般为内存中的临时对象,可以用来传递参数或对实体进行补充描述。
  • 工厂(Factories):主要用来创建实体,目前架构实践中一般采用IOC容器来实现工厂的功能。
  • 仓库(Repositories):用来管理实体的集合,封装持久化框架。
  • 服务(Services):为上层建筑提供可操作的接口,负责对领域对象进行调度和封装,同时可以对外提供各种形式的服务。

当然,DDD中还提出了聚合和聚合根(Aggregate Root)的概念,不过我们在实践过程发现聚合根有问题复杂化的倾向,用传统的聚合、组合等概念去描述领域对象之间的关系更容易理解,所以这里对这个概念就不做介绍了。

事务脚本和领域模型

Martin Fowler 2004年所著的企业应用架构模式(Patterns of Enterprise Application Architecture)中的第九章领域逻辑模式(Domain Logic Patterns)专门介绍了事务脚本(Transaction Script)和领域模型(Domain Model),理解这两种模式对设计和构建企业应用软件非常有帮助,所以有必要介绍一下。

事务脚本:事务脚本的核心是过程,通过过程的调用来组织业务逻辑,每个过程处理来自表现层的单个请求。大部分业务应用都可以被看成一系列事务,从某种程度上来说,通过事务脚本处理业务,就像执行一条条Sql语句来实现数据库信息的处理。事务脚本把业务逻辑组织成单个过程,在过程中直接调用数据库,业务逻辑在服务(Service)层处理。

事务脚本模式可以简单的通过UML图表示成这样:




由Action层处理UI层的动作请求,将Request中的数据组装后传递给BusinessService,BS层做简单的逻辑处理后,调用数据访问对象进行数据持久化,其中VO充当了数据传输对象的作用,一般是贫血的POJO,只具备getter和setter方法,没有状态和行为。

事务脚本模式的特点是简单容易理解,面向过程设计。对于少量逻辑的业务应用来说,事务脚本模式简单自然,性能良好,容易理解,而且一个事务的处理不会影响其他事务。不过缺点也很明显,对于复杂的业务逻辑处理力不从心,难以保持良好的设计,事务之间的冗余代码不断增多,通过复制粘贴方式进行复用。可维护性和扩展性变差。

领域模型:领域模型的特点也比较明显, 属于面向对象设计,领域模型具备自己的属性行为状态,并与现实世界的业务对象相映射。各类具备明确的职责划分,领域对象元素之间通过聚合和引用等关系配合解决实际业务应用和规则。可复用,可维护,易扩展,可以采用合适的设计模型进行详细设计。缺点是相对复杂,要求设计人员有良好的抽象能力。

领域模型对应的就是领域驱动设计中划分的领域层,这里就不详细讨论了。

在实际的设计中,我们需要根据具体的需求选择相应的设计模式。具备复杂业务逻辑的核心业务系统适合使用领域模型,简单的信息管理系统可以考虑采用事务脚本模式。
领域驱动设计实践

下面主要讲一下我们在构建企业级应用开发平台中对DDD的实践和扩展。

本人近年来一直在从事企业级应用开发平台的相关工作,GAP平台是我们的一个软件产品,用来解决企业级软件开发过程中复用、快速开发和过程规范等问题。设计这样一个平台,从底层的框架上就应该能够支撑复杂业务逻辑的系统构建,所以我们在大的架构设计思路上采用了领域驱动设计的思路,并根据实际采用的技术和要实现的功能对DDD的四层架构进行了细化和实现:

整个平台采用了JavaEE的技术及其相关的开源框架。系统的核心业务逻辑由Domain层处理,其中的业务服务(BusinessService)负责处理某个相对内聚的业务逻辑单元,同时对内对外提供本地或远程的服务。



下面是对各层的简要描述:

  • View:展示层,由于GAP平台主要面向B/S架构,展示层主要由web资源文件组成,包括JSP,JS和大量的界面控件,同时还采用了AJAX和Flex等RIA技术,负责向用户展现丰富的界面信息,并执行用户的命令。
  • Control:控制层,负责展示层请求的转发、调度和基础验证,同时自动拦截后台返回的Runtime异常信息,如果控制层需要与第三方系统交互,可以通过Action做远程的请求。
  • Domain:领域层,是系统最为丰富的一层,主要负责处理整个系统的业务逻辑。这一层包括业务服务和领域对象,同时负责系统的事务管理。其中业务服务可以提供本地调用和共享远程服务的功能。
  • Persistence:持久化层,主要负责数据持久化,支持O/R Mapping和JDBC。对数据源的访问提供多种方式。

另外,我们引入了Spring的IOC容器,系统的控制层、领域层和持久化层元素都有IOC容器统一管理,实现完全的接口分离和解耦。同时在控制、领域和持久化层都可以引用日志服务。

我们对领域驱动要素的定义上和原有的命名和含义上稍有区别。

原来的服务(Service),我们定义为业务服务(BusinessService),面向业务服务的架构是GAP平台的核心设计思想,一个业务服务可以由一个或多个领域模型和数据访问对象(DAO)组成,去实现一个完整的业务逻辑单元。业务服务主要负责事务处理和维护各个领域对象之间的关系,同时为上层访问提供本地和远程服务,服务类型包括Web Service,RMI等。

领域对象由实体(Entity)和值对象(VO)构成,实体类具备自己的属性和行为、状态,可以聚合VO,实体类之间可以有聚合关联等关系,可以由数据访问对象(DAO)进行持久化。

持久化由数据访问对象(DAO)实现,不处理业务逻辑,主要负责实体类的持久化。提供多种持久化方式(O/R Mapping和JDBC)。

那么如何在去实现领域驱动设计呢?我们总结了以下四个步骤:

  • 确定业务服务(Business Service):根据业务需求和功能模块划分,确定业务单元,每个Business Service是一个内聚的业务单元,覆盖相关的领域对象。
  • 定义领域对象(Entity, VO):根据业务单元的业务逻辑定义领域对象,通过UML方法和设计模式描述领域对象。
  • 定义领域对象的属性和关联关系:确定领域对象的各种属性和各个领域对象之间的关联关系。
  • 为领域对象增加行为:根据业务需求(系统用例和界面原型等)为领域对象增加行为,并定义哪些方法要被业务服务引用。

领域设计和SOA: http://www.jdon.com/jivejdon/thread/40576

在Yahoo的SOA讨论组一直持续着OO面向对象和SOA之间的讨论,涉及领域模型domain model, 消息格式message format 和服务设计考量等方面。

随着MDD/DDD不断深入和扩散,特别是在.NET领域的深入人心,曾经以SOA发起为荣耀的Java阵营有些固步自封,反而对领域建模DDD有排除作用,问题倒不是出在Hibernate ORM这些技术实现层面,而是在战略层面,因为JavaEE已经有各个厂商联合推出的SOA战略。

如今DDD火热,SOA阵营的人不得不正视与领域模型的融合,这其中发生了各种新旧观念的碰撞,InfoQ今天专门发表文章对这一大辩论进行总结,原文如下:InfoQ: OOD vs SOA Approach to SOA Domain Modeling

鉴于InfoQ中国可能对该文章翻译(需要客观观点看原文或等中文翻译),下面结合我个人DDD和SOA两方面经验,从我角度看看两者的讨论。

首先要搞清楚SOA和DDD是有区别的,SOA是以功能为划分边界,而DDD是以类为划分边界,将功能封装在类中作为方法。我个人更愿意把这两种都认为是最高意义上OO面向对象,就像现在面向函数语言Scala等也是以函数功能为划分。

初学者总是把面向函数或以功能划分和面向过程混同起来,两者本质区别就是有无边界划分。面向过程是很多功能模块混同在一起,一锅浆糊,而面向函数是以功能功能方法进行细分,松耦合解耦。

但正因为这个否定之否定,有点绕的概念,致使很多初学者使用SOA是以面向过程思维来使用,结合数据库SQL语言,我见过一个大型的SOA系统,其Web服务的定义实际就是数据表操作定义,SOA方法内部功能核心实际就是复杂一点的插入 删除 查询的SQL语句。

回头看看他们的讨论:

面向服务的建模技术(service oriented modeling ),比如具体到:面向领域的服务建模( DOSOM(Domain oriented service modeling) )是分解业务服务(SOA)的第一步,DDD是SOA的实现途径,通过基于业务功能结果基础上的领域划分,是迈向SOA的第一步。

一旦业务合约能够定义下来(由需求专家),OOD是实现服务实现的好方式,在SOA中分析需求不同我们在Jdon提倡的用例 四色原型 然后DDD等先后方法,它是首先定义业务合同business contract,是一种合约设计(Design by Contract),比如世界电信联盟NGOSS BOSS等都是这样SOA思路。

我曾经花不少时间研究NGOSS,实际上它和SOA一样,只不过是一个行业的SOA,同样存在SOA的缺点,只重视战略,战术指导不够,怎么实现都很含糊,结果很多电信BOSS系统都是基于关系数据库的SQL语句实现,出现我上面提到服务的方法实际就是数据库SQL语句的奇怪现象,导致系统难以维护和拓展,BUG不断,这时DDD正好可以弥补这样的缺憾。

当我们SOA中的服务是由富领域模型分解实现以后,带来的问题就是网络间传输富模型很浪费,那么大的有方法的富领域对象被序列化传输是性能杀手,现在一般使用只有setter/getter的贫血模型,实际是数据表的翻拍品,使用领域模型后,需要最小化服务之间的信息交换,可以使用一种主数据管理技术(MDM Master Data managerment)通过最小化的核心模型来实现消息。MDM我个人认为就是富领域模型的另外一种称谓。

具体谈到了Mortgage Industry Standards Maintenance Organization (MISMO)如何简化实体结构。MISMO适合作为相同行业中不同伙伴之间通讯的标准格式,对内部来说,核心领域模型使用是比较适合,因为通讯场景很少变化,也不必跨业务领域跨不同公司需要进行分享。在典型的企业内部需求中,灵活性和可维护性是必须的,而不是与其他企业通讯交换数据。

对于领域模型和SOA如何结合,我过去在Jdon帖子中也提出,SOA的服务内部委托领域模型的方法实现,SOA的服务对外是一个协定接口,打开SOA服务具体实现再看,你找不到核心功能实现代码,因为被分解到领域模型的方法中实现了,这点可以从JiveJdon的案例中可见一斑,这也是该讨论中有人分享他们的经验:Domain data is the classes that encapsulate information needed to implement services.This uses the classic object/relational mapping.

领域模型是用类来封装原本需要在服务中实现的信息数据,然后用经典的ORM映射。对于后面一句我也不敢沟通,如果说领域模型是SOA服务的分解实现,ORM则是领域模型的分解实现,有时我们不一定用ORM,用NoSQL也可以啊。这样说,会让很多初学者感觉绕了一圈还是落实到数据库,只不过中间加了多个环节,反而复杂,有脱裤子放屁嫌疑。其实虽然没多了环节,主角核心不一样了,现在主角核心是领域模型,不是数据表了。

讨论中David Tildesley 提到使用color modelling也就是jdon经常谈的四色建模,有颜色的建模技术,四色原型,结合原型领域形态(ADS)archetype domain shape 等,可以导向以组件为核心的建模方向,它认为传统的OO是以类为划分边界,有违SOA初衷。他认为每个业务组件都有其核心实体(四色原型中绿色或粉红色),他们由黄色的角色解耦。

从SOA和DDD定位来看,SOA更注重联系,外在关系,而DDD更注重内视,这是两种根本文化,就像有的人很注重人际关系;而有的人注重内修,学这个学那个,提高自己能力。前者在人多大企业公务员有好处,后者在私企或外企可以见长,当然如果两者兼具就更好,可惜能做得都好的人不多见。

我在下面另外帖子里给出:四色UML = SOA + DDD,当我们用彩色UML color UML分析需求以后,以后实现之路到底是以MI活动组件为核心架构,还是以PPT等静态组件为核心架构,是两种道路选择,当然,现在DCI好像正在模糊两者边界,静观其变:

http://www.jdon.com/jivejdon/thread/40577#23133002

相关 [领域 设计 实践] 推荐:

领域驱动设计和实践

- - 博客园_知识库
  软件系统面向对象的设计思想可谓历史悠久,20世纪70年代的Smalltalk可以说是面向对象语言的经典,直到今天我们依然将这门语言视为面向对象语言的基础. 随着编程语言和技术的发展,各种语言特性层出不穷,面向对象是大部分语言的一个基本特性,像C++、Java、C#这样的静态语言,Ruby、Python这样的动态语言都是面向对象的语言.

领域驱动设计和实践-转载

- - 人月神话的BLOG
引言(原文: http://www.kuqin.com/system-analysis/20110912/264696.html). 软件系统面向对象的设计思想可谓历史悠久,20世纪70年代的Smalltalk可以说是面向对象语言的经典,直到今天我们依然将这门语言视为面向对象语言的基础. 随着编程语言和技术的发展,各种语言特性层出不穷,面向对象是大部分语言的一个基本特性,像C++、Java、C#这样的静态语言,Ruby、Python这样的动态语言都是面向对象的语言.

谈领域驱动的设计

- - 人月神话的BLOG
最近一直在学习领域驱动设计,发现领域驱动设计的核心仍然是传统的面向对象分析设计的思路,但是却可以很好的和现有的组件化架构,分层架构,SOA服务等相关内容更好的融合. 对于传统的EA企业架构分析在分解到最底层的时候,很适合自然过渡到领域驱动设计的思路上来. 另外对于现有的基于NoSQL数据库的信息系统开发,领域驱动设计更是必须具备的系统分析和建模思路.

领域驱动设计系列(1)通过现实例子显示领域驱动设计的威力

- - 博客园_知识库
  曾经参与过系统维护或是在现有系统中进行迭代开发的软件工程师们,你们是否有过这样的痛苦经历:当需要修改一个Bug的时候,面对一个类中成百上千行的代码,没有注释,千奇百怪的方法和变量名字,层层嵌套的方法调用,混乱不堪的结构,不要说准确找到Bug所在的位置,就是要清晰知道一段代码究竟是做了什么也非常困难.

Boticca 把 Etsy 模式带到高端全球设计领域

- Kofai - 36氪
今年年初,爱沙尼亚珠宝设计师 Krista Raak 在巴黎的一个小型艺术品市场出售手工刺绣项链,后来她的作品被 Boticca.com 创始人发现,然后受邀加入该公司的全球首饰市场,Raak 一直想把自己的作品走出街旁集市和精品店,于是欣然接受. 合作三个月之后,Raak 已经获得大约1万美元的销售额,她拿80%.

淘宝响应式WebUI设计实践

- - Taobao UED Team
感谢贷岩的邀请,我在本期奶茶会上做了“响应式设计实践”的分享,是接着上一次的话题进一步讨论“如何实现”. 响应式Web设计(Responsive Web design)是当下比较流行的话题,什么是响应式设计. 简单讲,页面的设计与开发应当根据用户行为以及设备环境(系统平台、屏幕尺寸、屏幕定向等)进行相应的响应和调整.

数据库设计的最佳实践

- - CSDN博客数据库推荐文章
1、使用定义明确的表或列名,并保持一致(例如,School、StudentCourse、CourseID). 2、使用单数形式的表名(即,用StudentCourse而非StudentCourses). 表代表了实体的合集,不需要复数形式. 否则你将在定义表时不得不使用“{”、“[”等字符(即为了访问表Student Course,你须得书写“Student Course”.

HBase 原理、设计与优化实践

- - leejun_2005的个人页面
HBase —— Hadoop Database的简称,Google BigTable的另一种开源实现方式,从问世之初,就为了解决用大量廉价的机器高速存取海量数据、实现数据分布式存储提供可靠的方案. 从功能上来讲,HBase不折不扣是一个数据库,与我们熟悉的Oracle、MySQL、MSSQL等一样,对外提供数据的存储和读取服务.

如何实践设计原则

- - CSDN博客推荐文章
大家都知道遵循设计原则是开发高质量软件的重要基础,但实际运用时并不容易. Booch在<< 面向对象分析与设计>>中提出了四个基础原则:. 抽象   核心思想是不变性的概念. 去除不关心的属性,而强化重要的属性,帮助人们思考要做什么. 封装  核心是分离关注和信息隐藏,让程序借助最少的工作进行可靠的修改.

ActiveMQ架构设计与最佳实践

- - 深入一点,你会更加快乐
    ActiveMQ是最常用、特性最丰富的消息中间件,通常用于消息异步通信、调用解耦等多种场景,是JMS规范的实现者之一.     ActiveMQ提供两种可供实施的架构模型:“M-S”和“network bridge”;其中“M-S”是HA方案,“网络转发桥”用于实现“分布式队列”.     Master-Slave模型下,通常需要2+个ActiveMQ实例,任何时候只有一个实例为Master,向Client提供"生产"、“消费”服务,Slaves用于做backup或者等待Failover时角色接管.