关于DDD领域模型的基础知识

领域驱动模型 Domain-Driven Design,DDD

为什么出现领域模型

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

从啥是领域模型说起

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

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

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

领域模型(Domain Model)是一个商业建模范畴的概念,他和软件开发并无一丝一毫的关系,即使一个企业他不开发软件,他也具备他的业务模型,所有的同行业的企业他们的业务模型必定有非常大的共性和内在的规律性,由这个行业内的各个企业的业务模型再向上抽象出来整个行业的业务模型,这个东西即 “领域模型”。

而软件开发呢?一个并没有行业经验积累的软件公司,它开发的软件,基本上完全是需求驱动,而不是领域模型驱动。只有具备了领域模型积累的公司才有资格去谈领域模型驱动软件开发。

OSChina 上,红薯发表的一篇 《领域模型的概念》参见参考资料中的第一条。

为什么要用领域模型

降低与 OO 建模之间的表示差异。


领域模型的分类

1. 失血模型

失血模型请看 文章 中列举的第一种模型,简单来说,就是 domain object 只有属性的 getter/setter 方法,没有任何业务逻辑。

2. 贫血模型

贫血模型请看 文章 中列举的第二种模型,简单来说,就是 domain ojbect 包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到 Service 层。

Service(业务逻辑,事务封装) ---> DAO ---> domain object

这种模型的优点:

  1. 各层单向依赖,结构清楚,易于实现和维护
  2. 设计简单易行,底层模型非常稳定

这种模型的缺点:

  1. domain object 的部分比较紧密依赖的持久化 domain logic 被分离到 Service 层,显得不够 OO
  2. Service 层过于厚重

3. 充血模型

充血模型和第二种模型差不多,所不同的就是如何划分业务逻辑,即认为,绝大多业务逻辑都应该被放在 domain object 里面 (包括持久化逻辑),而 Service 层应该是很薄的一层,仅仅封装事务和少量逻辑,不和 DAO 层打交道。

Service(事务封装) ---> domain object <---> DAO

这种模型的优点:

  1. 更加符合 OO 的原则
  2. Service 层很薄,只充当 Facade 的角色,不和 DAO 打交道。

这种模型的缺点:

  1. DAO 和 domain object 形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。
  2. 如何划分 Service 层逻辑和 domain 层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能导致整个结构的混乱无序。
  3. 考虑到 Service 层的事务封装特性,Service 层必须对所有的 domain object 的逻辑提供相应的事务封装方法,其结果就是 Service 完全重定义一遍所有的 domain logic,非常烦琐,而且 Service 的事务化封装其意义就等于把 OO 的 domain logic 转换为过程的 Service TransactionScript。该充血模型辛辛苦苦在 domain 层实现的 OO 在 Service 层又变成了过程式,对于 Web 层程序员的角度来看,和贫血模型没有什么区别了。

4. 胀血模型

基于充血模型的第三个缺点,有同学提出,干脆取消 Service 层,只剩下 domain object 和 DAO 两层,在 domain object 的 domain logic 上面封装事务。

domain object(事务封装,业务逻辑) <---> DAO

似乎 Ruby on Rails 就是这种模型,他甚至把 domain object 和 DAO 都合并了。

该模型优点:

  1. 简化了分层
  2. 也算符合 OO

该模型缺点:

  1. 很多不是 domain logic 的 service 逻辑也被强行放入 domain object ,引起了 domain ojbect 模型的不稳定
  2. domain object 暴露给 web 层过多的信息,可能引起意想不到的副作用。

在这四种模型当中,失血模型和胀血模型应该是不被提倡的。而贫血模型和充血模型从技术上来说,都已经是可行的了。但是我个人仍然主张使用贫血模型。其理由:

  1. 参考充血模型第三个缺点,由于暴露给 web 层程序拿到的还是 Service Transaction Script,对于 web 层程序员来说,底层 OO 意义丧失了。

  2. 参考充血模型第三个缺点,为了事务封装,Service 层要给每个 domain logic 提供一个过程化封装,这对于编程来说,做了多余的工作,非常烦琐。

  3. domain object 和 DAO 的双向依赖在做大项目中,考虑到团队成员的水平差异,很容易引入不可预知的潜在 bug。

  4. 如何划分 domain logic 和 service logic 的标准是不确定的,往往要根据个人经验,有些人就是觉得某个业务他更加贴近 domain,也有人认为这个业务是贴近 service 的。由于划分标准的不确定性,带来的后果就是实际项目中会产生很多这样的争议和纠纷,不同的人会有不同的划分方法,最后就会造成整个项目的逻辑分层混乱。这不像贫血模型中我提出的按照是否依赖持久化进行划分,这种标准是非常确定的,不会引起争议,因此团队开发中,不会产生此类问题。

  5. 贫血模型的 domain object 确实不够 rich,但是我们是做项目,不是做研究,好用就行了,管它是不是那么纯的 OO 呢?其实我不同意 firebody 认为的贫血模型在设计模型和实现代码中有很大跨越的说法。一个设计模型到实现的时候,你直接得到两个类:一个实体类,一个控制类就行了,没有什么跨越。


领域驱动设计的经典分层架构

领域驱动设计的经典分层架构

1. 用户界面 / 展现层

负责向用户展现信息以及解释用户命令。更细的方面来讲就是:

请求应用层以获取用户所需要展现的数据;
发送命令给应用层要求其执行某个用户命令;

2. 应用层

很薄的一层,定义软件要完成的所有任务。对外为展现层提供各种应用功能(包括查询或命令),对内调用领域层(领域对象或领域服务)完成各种业务逻辑,应用层不包含业务逻辑。

3. 领域层

负责表达业务概念,业务状态信息以及业务规则,领域模型处于这一层,是业务软件的核心。

4. 基础设施层

本层为其他层提供通用的技术能力;提供了层间的通信;为领域层实现持久化机制;总之,基础设施层可以通过架构和框架来支持其他层的技术需求;


设计领域模型的一般步骤

  1. 根据需求建立一个初步的领域模型,识别出一些明显的领域概念以及它们的关联,关联可以暂时没有方向但需要有(1:1,1:N,M:N)这些关系;可以用文字精确的没有歧义的描述出每个领域概念的涵义以及包含的主要信息;
  2. 分析主要的软件应用程序功能,识别出主要的应用层的类;这样有助于及早发现哪些是应用层的职责,哪些是领域层的职责;
  3. 进一步分析领域模型,识别出哪些是实体,哪些是值对象,哪些是领域服务;
  4. 分析关联,通过对业务的更深入分析以及各种软件设计原则及性能方面的权衡,明确关联的方向或者去掉一些不需要的关联;
  5. 找出聚合边界及聚合根,这是一件很有难度的事情;因为你在分析的过程中往往会碰到很多模棱两可的难以清晰判断的选择问题,所以,需要我们平时一些分析经验的积累才能找出正确的聚合根;
  6. 为聚合根配备仓储,一般情况下是为一个聚合分配一个仓储,此时只要设计好仓储的接口即可;
  7. 走查场景,确定我们设计的领域模型能够有效地解决业务需求;
  8. 考虑如何创建领域实体或值对象,是通过工厂还是直接通过构造函数;
  9. 停下来重构模型。寻找模型中觉得有些疑问或者是蹩脚的地方,比如思考一些对象应该通过关联导航得到还是应该从仓储获取?聚合设计的是否正确?考虑模型的性能怎样,等等;

领域建模是一个不断重构,持续完善模型的过程,大家会在讨论中将变化的部分反映到模型中,从而是模型不断细化并朝正确的方向走。领域建模是领域专家、设计人员、开发人员之间沟通交流的过程,是大家工作和思考问题的基础。


为什么建立一个领域模型是重要的

领域驱动设计告诉我们,在通过软件实现一个业务系统时,建立一个领域模型是非常重要和必要的,因为领域模型具有以下特点:

  1. 领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分;
  2. 领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等;
  3. 领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助;
  4. 领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造;
  5. 领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型,所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求;
  6. 要建立正确的领域模型并不简单,需要领域专家、设计、开发人员积极沟通共同努力,然后才能使大家对领域的认识不断深入,从而不断细化和完善领域模型;
  7. 为了让领域模型看的见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的表达方式,代码或文字描述也能表达领域模型;
  8. 领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化;

参考资料

赞赏