零、概述
很多应用程序最主要的复杂性并不在技术上,而是来自领域本身、用户的活动或业务。
领域驱动设计是一种设计方法,试图解决软件难以理解、难以演化的问题。领域驱动设计试图用围绕业务概念来构建领域模型的方式来控制业务的复杂性。
一、领域模型
领域模型是经过严格组织并精心选择的抽象知识,对知识进行有选择的简化和有组织的结构化。适当的模型使人理解信息的意义,并专注于问题相关的信息。
1. 消化知识
开发人员与领域专家通过不断的沟通和协作,消化专业知识构建领域模型,并且在迭代过程中不断地完善模型。
知识消化是一种探索,永无止境;在知识消化过程中领域模型不断精化,使得开发人员不断学习重要的业务原理,同时领域专家也通过不断地提炼专业知识完善自身的理解。
业务活动和规则的消化:业务活动和规则同实体一样,也是领域的中心。建模不应局限于寻找实体和值对象,同时还要消化业务活动和规则。通过将规则消化为概念(如抽象为策略对象),能够更好的保护和共享知识。
2. 模型语言
模型应该为开发人员和领域专家提供一种用于相互交流的通用语言,减少沟通障碍成本。讨论系统时要结合模型,使用模型中的元素来描述场景,并且使用尽可能简洁的语言,然后将新思想应用到图和代码中。
图:应该使用简化的图,图中只包含对象模型的重要概念部分。(综合性的大图反而会失去沟通和解释能力) 在澄清一些要点时,需要添加一些文本和非标准的符号。模型不是图,图的目的是为了帮助表达和解释模型。
文档:文档应作为代码和口头交流的补充。(文档不该再重复表示代码已明确表达出的内容)设计文档的最大价值是解释模型的概念,帮助在代码的细节中指引方向。如果文档在项目中的角色不太重要,可以将其淘汰,而对于与项目活动紧密交互的文档应当需要保持更新。
解释性模型:解释模型时,不必拘泥于软件设计相关的技术模型。可以专门为某个特殊主题定制一些表达力更强的风格。
3. 绑定模型和实现
建立的模型需要在分析和程序设计阶段都能发挥良好的作用,既能反映深层次的领域概念,又能在软件中更加自然的实现模型;不能因为技术考虑而削弱分析的功能,也不能接收只反映领域概念却舍弃软件设计原则的拙劣设计。
在软件开发中设计是无处不在的,编写代码和建模工作不应该分离。任何参与建模的人员,无论在项目中主要职责是什么,都必须花时间了解代码;开发人员需要学会用代码来表达模型,并不用程度的参与模型讨论并与领域专家保持联系。
二、模型驱动设计的构造块
1. 分离领域
分层架构:
- 用户界面层:负责向用户显示信息和解释用户指令
- 应用层:定义软件要完成的任务,指挥表达领域概念的对象来解决问题。应用层要尽量简单,不包含业务规则和知识,只为下一层的领域对象协调任务,分配工作。
- 领域层:表达业务概念,业务状态信息和业务规则。领域层是业务软件的核心。(处理业务规则的是领域层,而不是应用层)
- 基础设施层:为上面各层提供通用的技术能力。
每一层内具有内聚性并只依赖于它的下层,且只与上层进行松散连接。领域对象将重点放在如何表达领域模型上,而无需考虑自身的显示和存储问题。
2. 模型的表示
1)控制关联:
- 规定一个遍历方向(理解领域中更关心哪个方向的关联,如更关心国家有哪些总统,还是总统是哪个国家的,从而确定关联的遍历方向)
- 添加一个限定符,以便有效减少多重关联(如添加时段的限制,某个国家在某个时段内只能有一个总统,从而将一对多的关联转换为一对一的关联)
- 消除不必要的关联
2)实体Entity:
实体由标识定义,而不是由属性定义,实体是一种贯穿整个生命周期(甚至经历多种形式)的抽象的连续性。在定义标识时,要确保这种操作为每个对象生成唯一的结果。
并不是所有对象都需要这种标识,只有在需要时才将对象表示为实体。
在对Entitty进行建模时,应该只添加对概念至关重要的行为和这些行为所必须的属性;而将其他属性和行为转移到与核心实体关联的其他对象中。
3)值对象Value Object
用于描述领域的某个方面而本身没有概念标识的对象称为Value Object(值对象)。当只关心一个模型元素的属性时,应把它归类为值对象。
设计值对象时,可以采用复制、共享、保持不变的方式。把值对象设计为不可变,可以极大的简化实现,确保共享和引用传递的安全性。
4)服务Service
当领域中的某个重要的过程或转换操作不属于实体或值对象的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为Service。
各个层都存在Service
5) Module
Module将紧密概念关系的模型元素集中到一起,将具有相关职责的对象元素聚合到一起,把建模和设计工作集中到单一Module中。这通常会实现Module之间的低耦合,如果效果不理想则应该尝试更改模型或找到另一个可作为Module基础的概念。
3. 领域对象的生命周期
在生命周期的开始阶段,使用 Factory(工厂)创建和重建复杂对象,并使用 Aggregate 封装他们的内部结构,在生命周期的中间和末尾使用 Repository(存储库)来提供查找和检索持久对象并封装庞大基础设施的手段。
1)聚合
聚合Aggregate:聚合是一组相关对象的集合,每个聚合都有一个边界和聚合根(root)。边界定义了聚合内部有什么,聚合根是聚合中所包含的一个特定的 Entity。边界内部的实体可以相互引用,而聚合根是唯一允许外部对象保持对它引用的元素,外部对象除了根以外看不到其他对象(对内部成员的临时引用可以被传递出去,但只在一次操作中有效)。
根控制访问:不能绕过聚合根来修改内部对象,这样有利于确保聚合内的所有对象满足所有固定规则,也可以保证在任何状态变化时聚合作为一个整体满足固定规则。
2)工厂
工厂 Factory:将创建复杂对象的实例和聚合的职责转移给一个单独的对象。
Factory的选择:
- 向一个已存在的聚合添加元素,可以在聚合根上添加一个工厂method,从而对外隐藏实现细节并保证聚合的完整性。
- 如果一个对象的创建主要使用另一个对象的数据,可以在后者对象上添加一个工厂method(即使被创建对象不在创建对象的聚合内)
- 找不到合适的地方隐藏创建细节时,可以创建一个专有的Factory。聚合通常由一个专有的Factory来创建,且Factory负责把对根的引用传递出去。(聚合内部的工厂应该把访问限制在聚合内部)
2)Repository
在访问持久对象时,当不易通过遍历方式访问聚合根时,这一小部分的对象必须能通过基于对象属性的搜索来全局访问。
其他对象则不应该使用这种方式,毫无约束的数据库查询会破坏领域对象的封装和Aggregate。应该通过聚合根导航来获取这些数据。(不能随便就通过数据库查询获得聚合内部对象)
Repository将数据库访问技术和策略封装起来,让客户始终聚焦于模型,而将所有对象存储和访问操作交给Repository来完成。只为那些需要直接访问的聚合根提供Repository。