软件系统面向对象的设计思想可谓历史悠久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中的数据组装后传递给BusinessServiceBS层做简单的逻辑处理后调 用数据访问对象进行数据持久化其中VO充当了数据传输对象的作用一般是贫血的POJO只具备getter和setter方法没有状态和行为。
事务脚本模式的特点是简单容易理解面向过程设计。对于少量逻辑的业务应用来说事务脚本模式简单自然性能良好容易理解而且一个事务的处 理不会影响其他事务。不过缺点也很明显对于复杂的业务逻辑处理力不从心难以保持良好的设计事务之间的冗余代码不断增多通过复制粘贴方式进行复用。 可维护性和扩展性变差。
领域模型
领域模型的特点也比较明显 属于面向对象设计领域模型具备自己的属性行为状态并与现实世界的业务对象相映射。各类具备明确的职责划分领域对象元素之间通过聚合和引用等关系配合 解决实际业务应用和规则。可复用可维护易扩展可以采用合适的设计模型进行详细设计。缺点是相对复杂要求设计人员有良好的抽象能力。
领域模型对应的就是领域驱动设计中划分的领域层这里就不详细讨论了。
在实际的设计中我们需要根据具体的需求选择相应的设计模式。具备复杂业务逻辑的核心业务系统适合使用领域模型简单的信息管理系统可以考虑采用事务脚本模式。
领域驱动设计实践
下面主要讲一下我们在构建企业级应用开发平台中对DDD的实践和扩展。
本人近年来一直在从事企业级应用开发平台的相关工作GAP平台是我们的一个软件产品用来解决企业级软件开发过程中复用、快速开发和过程规范 等问 题。设计这样一个平台从底层的框架上就应该能够支撑复杂业务逻辑的系统构建所以我们在大的架构设计思路上采用了领域驱动设计的思路并根据实际采用的 技术和要实现的功能对DDD的四层架构进行了细化和实现
整个平台采用了JavaEE的技术及其相关的开源框架。系统的核心业务逻辑由Domain层处理其中的业务服务BusinessService负责处理某个相对内聚的业务逻辑单元同时对内对外提供本地或远程的服务。
下面是对各层的简要描述
View展示层由于GAP平台主要面向B/S架构展示层主要由web资源文件组成包括JSPJS和大量的界面控件同时还采用了AJAX和Flex等RIA技术负责向用户展现丰富的界面信息并执行用户的命令。
Control控制层负责展示层请求的转发、调度和基础验证同时自动拦截后台返回的Runtime异常信息如果控制层需要与第三方系统交互可以通过Action做远程的请求。
Domain领域层是系统最为丰富的一层主要负责处理整个系统的业务逻辑。这一层包括业务服务和领域对象同时负责系统的事务管理。其中业务服务可以提供本地调用和共享远程服务的功能。
Persistence持久化层主要负责数据持久化支持O/R Mapping和JDBC。对数据源的访问提供多种方式。
另外我们引入了Spring的IOC容器系统的控制层、领域层和持久化层元素都有IOC容器统一管理实现完全的接口分离和解耦。同时在控制、领域和持久化层都可以引用日志服务。
我们对领域驱动要素的定义上和原有的命名和含义上稍有区别。
原来的服务Service我们定义为业务服务BusinessService面向业务服务的架构是GAP平台的核心设计思想一个 业务服务可以由一个或多个领域模型和数据访问对象DAO组成去实现一个完整的业务逻辑单元。业务服务主要负责事务处理和维护各个领域对象之间的关 系同时为上层访问提供本地和远程服务服务类型包括Web ServiceRMI等。
领域对象由实体Entity和值对象VO构成实体类具备自己的属性和行为、状态可以聚合VO实体类之间可以有聚合、关联等关系可以由数据访问对象DAO进行持久化。
持久化由数据访问对象DAO实现不处理业务逻辑主要负责实体类的持久化。提供多种持久化方式O/R Mapping和JDBC。
那么如何在去实现领域驱动设计呢我们总结了以下四个步骤
确定业务服务Business Service根据业务需求和功能模块划分确定业务单元每个Business Service是一个内聚的业务单元覆盖相关的领域对象。
定义领域对象Entity VO根据业务单元的业务逻辑定义领域对象通过UML方法和设计模式描述领域对象。
案例――网上书店
为了更好的理解领域驱动设计我们基于以上设计方法实现了一套简单的网上书店系统。
网上书店系统是采用DDD设计思想构建的一个应用系统示例。通过网上书店系统可以快速理解领域驱动设计。该系统实现网上书店的常用功能包括 浏览书籍、挑选书籍、提交订单、查看订单、自动折扣、处理订单、取消订单等。未登录用户可以浏览和挑选书籍已登录用户可以提交和查看自己相关的订单管 理员可以处理订单。
经过业务抽象即使是这样一个简单的业务场景也包含了很多领域对象例如订单、账户、书籍、购物车、购物项、折扣等通过分析和设计我们可以得到这样的设计图为了查看方便图中的类隐藏了属性信息
BookStoreAction负责处理展现层的请求并把请求转发给业务服务IBookStoreBS业务服务负责调度上图中显示的领域对象处理该场景的所有业务。
其中领域对象和现实业务的对应关系为
Account――账户
Order――订单
Book――书籍
Cart――购物车
Item――订单项
Discount――折扣
与事务脚本的编程模式不同领域驱动设计不是把业务逻辑放在BSBusinessService中而是由具备属性、行为和状态的领域对象 处理。例如Order类如果是贫血的POJO那它内部只有与数据表字段对应的属性以及getter和setter方法而在领域驱动设计中则是一个 相对独立的、能够处理自身关联业务的领域对象。在本系统中我们对Order的描述如下
订单的实现类是gap.template.bookstore.model.Order类中除了联系方式、邮寄地址等基本属性外还有以下领域相关的行为
cancel(...)取消订单把订单和相关item的状态设置为“已取消”然后委托Dao进行持久化。
dispose(...)处理订单首先更新订单项的状态然后委托Dao持久化订单数据。
reSubmit、setItemsStatus......
通过以上的描述我们可以看到Order类基本上覆盖了现实世界中订单这个业务的所有行为和状态是相对内聚的这样的特性使其复用性大大增 加 即使未来开发新的模块涉及到订单业务的可以直接复用Order类。同时在后期维护中如果我想了解订单的业务直接读Order的代码就可以了。
从上图中我们还可以清晰的看到各个领域对象之间的关系。Order和Cart都聚合了Item对应都是1...nItem聚合了Book对应关系1...1。Order分别与折扣、账户发生关联和调用等等整个网上书店的场景就这样描述出来了。
另外不要忘了BS除了起到基础设施的作用外事务管理和服务共享它还要负责调度和维护领域对象之间的关系。因为总会有些业务逻辑既不 属于这个领域对象也不属于那个那这部分业务由谁来处理呢由BS来处理。例如在管理员处理订单这个场景中首先需要根据订单信息获取账户根据账户信 息确定折扣率同时进行余额校验如果校验通过就会调用订单对象的dispose方法处理订单这个场景会涉及到Order、Account、 Discount等对象这样的业务逻辑应该由BS实现。
IBookStoreDao是数据访问对象可以被BS调用用来持久化对象也可以被领域对象引用用来持久化自身。
通过以上的描述我们可以看到整个设计和实现是优雅、清晰的。业务逻辑没有堆积在BS中而是分散在BS和各个领域对象中服务和对象都与现实世界的业务息息相关无论是对领域专家、开发人员和后期维护人员都能这种方式中获得自己需要的内容。
总结
我们采用领域驱动设计相对比较早就我个人的检验和实践而言DDD对构建企业级应用开发平台和大型核心业务系统的作用是非常明显的无论是在产品的稳定性、扩展性、可维护性、生命周期等方面都有显著的提升。
但是由于这样那样的原因复杂度、工期、开发人员能力限制等等很多人会不自觉的抵制采用DDD有时候一个软件项目重写了两次第二次依 然不去做良好的设计。事实上采用了DDD的设计方法我们的设计阶段已经变得非常轻量级和敏捷了开发人员只要能够把领域模型之间的关系画出来并描述说 明并与需求人员达成一致那么做出来的东西基本上是靠谱的。
在技术领域只有主动的尝试和提升效果才是最明显的。很多人问过我如何开始学习和实践XXX其实很简单现在就开始吧
参考资料
《 领域驱动设计―软件核心复杂性应对之道》Evans Eric著Addison-Wesley出版社
《企业应用架构模式》 Martin Fowler著 Addison-Wesley出版社
原文http://kb.cnblogs.com/page/129080/
原文链接:https://www.f2er.com/javaschema/283946.html