关于领域驱动设计的理解
领域驱动设计是什么
2004 年埃里克·埃文斯(Eric Evans)出版了《领域驱动设计》(Domain-Driven Design
–Tackling Complexity in the Heart of Software)这本书,从此领域驱动设计(Domain Driven Design,简称 DDD)诞生。
DDD认为,软件的核心是其为用户解决领域相关的问题的能力,所有其他特性,不管有多么重要,都要服务于这个基本目的。
领域驱动设计是一种思维方式,它提供了一整套完整的设计实践、技术和原则,加速了复杂领域的软件项目的开发。
领域驱动设计的主要内容
构造块和柔性设计
此部分也称为战术设计,主要用于应对相对小型的系统模型,主要依靠面向对象的分析技术。
构造块介绍了DDD中模型中的组成要素,柔性设计介绍了一些模式,用于控制模型的复杂性,以应对模型重构带来的冲击。
战略设计
随着系统的不断发展,模型会变得越来越复杂,战略设计是为了应对这种大模型的技术,包括上下文、精炼和大型结构。
领域模型
主流的开发架构
目前主流的开发结构是使用Spring相关组件,采用的典型的分层架构。
-
界面层:包含了界面相关内容,主要是html、js和css,而且目前普遍会采用前后端分离的方式,将界面层独立服务器部署。
-
应用层:主要包含了java代码,用于处理业务操作逻辑,其又划分为三层,controller、service和dao。
- controller为控制层,用于接收请求并分发至serivce,这一层不会写复杂的逻辑
- service为服务层,主要逻辑在这一层实现
- dao为数据访问层,用于接入数据库
-
数据层:为数据库存储,通常会使用关系型数据库
在这种架构结构中,业务的模型体现在数据库表的设计中,应用层的本质是数据库的一层外皮,在这层外皮中,用java实现了业务行为逻辑,在应用层中也存在Entity的概念,是指数据库表的映射(与DDD的Entity是不同的概念)。这种架构设计主要有两个有点:一个是技术层面上更容易理解,对业务模型进行了逻辑划分,Entity负责访问数据库,Service用于处理业务逻辑;另一个是这种分层架构可以很容易实现分布式的拆分,将层独立服务器部署。
但在DDD中,认为这种架构把领域概念的元素划分的很零散,代码无法清楚的表示模型。
运用领域模型
领域模型的含义
在DDD中,更加强调业务抽象和面向对象编程,而不是过程式业务逻辑实现,领域模型是DDD的关键核心。领域模型可以通俗理解为面向对象的建模,也就是对现实世界的事物或问题的抽象建模,过去也会称之为仿真,现在有个更加时髦的词汇“数字孪生”。有效的建模是一个很困难的过程,抽象的过程并没太多有效的方法,主要的就是分析业务需求中的名词和动词,分别对应对象中的属性和行为,而更多的是要依靠不断的学习和消化业务,随着对业务的深入理解,逐渐拨开现象得到本质。
UBIQUITOUS LANGUAGE
DDD中推荐极限编程的模式,推崇项目所有相关人员在一起工作,在交流时需要一种统一的语言,以打破业务人员和技术人员在沟通上的障碍。
在DDD中,领域模型就是UBIQUITOUS LANGUAGE(通用语言),其中包含了类和主要操作的名称,UBIQUITOUS LANGUAGE的更改就是对模型的更改。
MODEL-DRIVEN DESIGN
模型驱动设计的含义是软件系统的各个部分都要反映出领域模型,程序的代码是领域模型的表达,任何部分的改变都是模型的改变。
结合通用语言和模型驱动设计,业务分析和系统设计的分离应调整为依赖统一的领域模型,包括编码也同样依赖领域模型,在使用DDD的项目团队中,领域模型是日常讨论必须使用的媒介,任何环节的修改都是领域模型的修改。
模型驱动设计构造块
分离领域
DDD中领域是重点关注的点,要想处理复杂的任务程序,需要做到关注点的分离,在设计中能够分别处理。这里采用的经典的分层架构,分层架构的基 本原则是层中的任何元素都仅依赖于本层的其他元素或其下层的元素 ,下层不可依赖上层,分层的设计可以使各层具有内聚性。
DDD的推荐分层架构:
- 用户界面层(或表示层):负责向用户显示信息和解释用户指令。这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人
- 应用层:定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,也是与其他系统的应用层进行交互的必要渠道应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使它们互相协作。它没有反映业务情况的状态,但是却可以具有另外一种状态,为用户或程序显示某个任务的进度
- 领域层(或模型层):负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心
- 基础设施层 :为上面各层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,
为用户界面层绘制屏幕组件,等等。基础设施层还能够通过架构框架来支持4个层次间的交互模式
这里的核心涵义是将领域分离,依托于其他层的支持,可以投入更多的精力去关注。
软件中所表示的模型
关联
关联是指模型对象之间的关系,包括一对一、一对多、多对多,在设计中应尽可能的减少关联,主要有以下三种方法:
(1) 规定一个遍历方向。
(2) 添加一个限定符,以便有效地减少多重关联。
(3) 消除不必要的关联。
ENTITY 实体
又称为REFERENCE OBJECT,是指 一些对象主要不是由它们的属性定义的,而是由唯一标识符定义。ENTITY可以是任何事物,只要满足两个条件即可 ,一是它在整个生命周期中具有连续性, 二是它的区别并不是由那些对用户非常重要的属性决定的。
这里应注意DDD中的ENTITY与现在主流的ENTITY不是同一个概念,主流的ENTITY更像EJB中的实体bean,是数据库表的在应用中的映射,用来进行数据库操作。
VALUE OBJECT 值对象
用于描述领域的某个方面而本身没有概念标识的对象称为VALUE OBJECT(值对象),我们只关心它们是什么,而不关心它们是谁。VALUE OBJECT经常作为参数在对象之间传递消息。它们常常是临时对象,在一次操作中被创建,然后丢弃。VALUE OBJECT可以用作ENTITY(以及其他VALUE)的属性 。VALUE OBJECT应该是不可变的,对值对象的操作通常可以通过复制和共享。
SERVICE 服务
当领域中的某个重要的过程或转换操作不是ENTITY或VALUE OBJECT的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为SERVICE。此外,应该使SERVICE成为无状态的。 SERVICE可以控制领域层中的接口的粒度,并且避免客户端与ENTITY和VALUE OBJECT耦合。
MODULE 模块
MODULE又称为Package。MODULE之间应该是低耦合的,而在MODULE的内部则是高内聚的 ,MODULE的名称应该是UBIQUITOUS LANGUAGE中的术语。
领域对象的生命周期
AGGREGATE
AGGREGATE就是一组相关对象的集合。每个AGGREGATE都有一个根(root)和一个边界(boundary)。边界定义了AGGREGATE的内部都有什么。根则是AGGREGATE所包含的一个特定ENTITY。对AGGREGATE而言,外部对象只可以引用根,而边界内部的对象之间则可以互相引用。
由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确保AGGREGATE中的对象满足所有固定规则,也可以确保在任何状态变化时AGGREGATE作为一个整体满足固定规则。
FACTORY
应该将创建复杂对象的实例和AGGREGATE的职责转移给单独的对象,这个对象本身可能没有 承担领域模型中的职责,但它仍是领域设计的一部分。提供一个封装所有复杂装配操作的接口,而且这个接口不需要客户引用要被实例化的对象的具体类。在创建AGGREGATE时要把它作为一 个整体,并确保它满足固定规则。
REPOSITORY
为每种需要全局访问的对象类型创建一个对象,这个对象相当于该类型的所有对象在内存中的一个集合的“替身”。通过一个众所周知的全局接口来提供访问。提供添加和删除对象的方法, 用这些方法来封装在数据存储中实际插入或删除数据的操作。提供根据具体条件来挑选对象的方法,并返回属性值满足查询条件的对象或对象集合(所返回的对象是完全实例化的),从而将实际的存储和查询技术封装起来。只为那些确实需要直接访问的AGGREGATE根提供REPOSITORY。 让客户始终聚焦于模型,而将所有对象的存储和访问操作交给REPOSITORY来完成。
实例代码
以下参考代码,基本符合以上的结构,可供参考。
具体代码可参考关联资源:https://download.csdn.net/download/codezxhy/89530520
通过重构来加深理解
领域模型很难一次性构建出俩,是需要从初始的表面逐渐进化为深层的模型,这种重构是必要的,每次重构都以为这一些深层次的模型的出现。对于这些必要的重构,在设计上就要支持这种改变,柔性设计就是为了支持这种改变。
将隐式概念转变为显示概念
概念的挖掘需要设计人员不断思考、通过重构来实现的,其中也提供了两种模式。
- 将过程建模为领域对象:在领域中一些复杂的逻辑过程应抽象为领域对象,可以通过SERVICE来实现
- 显示约束 SPECIFICATION:SPECIFICATION就是一个谓词,可用来确定对象是否满足某些标准,SPECIFICATION主要用来实现校验和查询选择
public class Test {
public static void main(String[] args) {
//挑选List中小于5的元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(4);
list.add(6);
//传统方式
for(int i = 0; i < list.size(); i++) {
int x = list.get(i);
if(x < 5) {
System.out.println(x);
}
}
//Stream方式 谓语
list.stream().filter(i -> i < 5).forEach(System.out::println);
}
}
柔性设计
柔性设计的本质是使模型更加清晰简单,减轻客户端的理解负担,进而实现让重构变得简单可行。
INTENTION-REVEALING INTERFACES 释意接口
类型名称、方法名称和参数名称组合在一起,共同形成了一个 INTENTION-REVEALING INTERFACE(释意接口),在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目的的,这 种方法可以使我们把注意力集中在项目上,并控制大型系统的复杂性。
SIDE-EFFECT-FREE FUNCTION 无副作用函数
我们可以宽泛地把操作分为两个大的类别:命令和查询。
多个规则的相互作用或计算的组合所产生的结果是很难预测的。开发人员在调用一个操作 时,为了预测操作的结果,必须理解它的实现以及它所调用的其他方法的实现。如果开发人员不 得不“揭开接口的面纱”,那么接口的抽象作用就受到了限制。如果没有了可以安全地预见到结 果的抽象,开发人员就必须限制“组合爆炸”,这就限制了系统行为的丰富性。
任何对系统状态产生的影响都叫副作用,返回结果而不产生副作用的操作称为函数。
尽可能把程序的逻辑放到函数中,因为函数是只返回结果而不产生明显副作用的操作。严格地把命令(引起明显的状态改变的方法)隔离到不返回领域信息的、非常简单的操作中。当发现了一个非常适合承担复杂逻辑职责的概念时,就可以把这个复杂逻辑移到VALUE OBJECT中,这 样可以进一步控制副作用。
举例:
Paint是绘画类,mixIn是一个有副作用的方法,会改变Paint的属性状态
public class Paint {
double volume;
int red;
int yellow;
int blue;
void minIn(Paint other) {
volume = volume + other.volume;
red = red + other.red;
yellow = yellow + other.yellow;
blue = blue + other.blue;
}
}
下面的代码把Paint的属性构建为一个值对象,并且值对象提供一个SIDE-EFFECT-FREE FUNCTION,这个函数的计算结果很容易理解,也很容易测试,因此可以安全地使用或与其他操作进行组合。由于它的安全性很高,因此复杂的调色逻辑真正被封装起来了。使用这个类的开发人员不必理解其实现。
public class Paint {
PigmentColor pigmentColor;
void minIn(Paint2 other) {
this.pigmentColor = this.pigmentColor.mixWith(other.pigmentColor);
}
}
public class PigmentColor {
int red;
int yellow;
int blue;
PigmentColor(int red, int yellow, int blue) {
this.red = red;
this.yellow = yellow;
this.blue = blue;
}
PigmentColor mixWith(PigmentColor other) {
return new PigmentColor(this.red + other.red, this.yellow + other.yellow, this.blue + other.blue);
}
}
ASSERTION 断言
把复杂的计算封装到SIDE-EFFECT-FREE FUNCTION中可以简化问题,但实体仍然会留有一些有
副作用的命令,使用这些ENTITY的人必须了解使用这些命令的后果。在这种情况下,使ASSERTION(断言)可以把副作用明确地表示出来,使它们更易于处理。
把操作的后置条件和类及AGGREGATE的固定规则表述清楚。如果在你的编程语言中不能直 接编写ASSERTION,那么就把它们编写成自动的单元测试。还可以把它们写到文档或图中(如果符合项目开发风格的话)。
CONCEPTUAL CONTOUR 概念轮廓
在连续的重构过程中观察发生变化和保证稳定的规 律性,并寻找能够解释这些变化模式的底层CONCEPTUAL CONTOUR
INTENTION-REVEALING INTERFACE使客户能够把对象表示为有意义的单元,而不仅仅是一些机 制。SIDE-EFFECT-FREE FUNCTION和ASSERTION使我们可以安全地使用这些单元,并对它们进行复杂的组合。CONCEPTUAL CONTOUR的出现使模型的各个部分变得更稳定,也使得这些单元更直观, 更易于使用和组合。
STANDALONE CLASS 独立的类
低耦合是对象设计的一个基本要素。尽一切可能保持低耦合。把其他所有无关概念提取到对象之外。这样类就变得完全独立了,这就使得我们可以单独地研究和理解它。每个这样的独立类都极大地减轻了因理解MODULE而带来的负担。
尽力把最复杂的计算提取到STANDALONE CLASS(独立的类)中,实现此目的的一种方法是从存在大量依赖的类中将VALUE OBJECT建模出来。
CLOSURE OF OPERATION 闭合操作
在适当的情况下,在定义操作时让它的返回类型与其参数的类型相同。如果实现者 (implementer)的状态在计算中会被用到,那么实现者实际上就是操作的一个参数,因此参数 和返回值应该与实现者有相同的类型。这样的操作就是在该类型的实例集合中的闭合操作。闭合 操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖。
由于返回值与实现 者的类型相匹配,因此它们可以像一系列过滤器一样被串接在一起。读写代码都变得很容易。它们并没有引入与选择子集无关的外来概念。 该模式可以实现链式操作,可以更加容易的形成声明式设计。
声明式设计
通常是指一种编程方式—把程序或程序的一部分写成一种可执行的规格。使用声明式设计时,软件实际上是由一些非常精确的属性描述来控制的。
声明式是相对命令式,举例来说:vue是声明式设计,JQuery是命令式设计
vue:
<div @click="()=>aalert('ok')>hello world</div>
JQuery:
$('#app')
.text('hello world')
.on('click', ()=>{
alert('ok')
})
应用分析模式
分析模式是一种概念集合,用来表示业务建模中的常见结构。它可能只与一个领域有关,也可能跨越多个领域。
将设计模式应用于模型
设计模式是用于解决一些编码问题的成熟方式,利用这些设计模式的理念,去解决领域建模中的问题。
STRATEGY 策略模式
我们需要把过程中的易变部分提取到模型的一个单独的“策略”对象中。将规则与它所控制 的行为区分开。按照STRATEGY设计模式来实现规则或可替换的过程。策略对象的多个版本表示 了完成过程的不同方式。
通常,作为设计模式的STRATEGY侧重于替换不同算法的能力,而当其作为领域模式时, 其侧重点则是表示概念的能力,这里的概念通常是指过程或策略规则。
COMPOSITE组合模式
将对象组织为树来表示部分—整体的层次结构。利用COMPOSITE,客户短可以对单独的对象和对象组合进行同样的处理。
战略设计
随着系统的增长,它会变得越来越复杂,当我们无法通过分析对象来理解系统的时候,就需要掌握一些操纵和理解大模型的技术了,战略设计原则必须把模型的重点放在捕获系统的概念核心,也就是系统的“远景”上。
保持模型的完整性
BOUNDED CONTEXT 限界上下文
任何大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现 bug、变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。
一个模型只在一个上下文中使用。这个上下文可以是代码的一个特定部分,也可以是某个特定团队的工作。明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设计模型的边界。在这些边界中严格保持模型的一致性,而不要受 到边界之外问题的干扰和混淆。
BOUNDED CONTEXT明确地限定了模型的应用范围,以便让团队成员对什么应该保持一致以及上下文之间如何关联有一个明确和共同的理解。在CONTEXT中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。在其他CONTEXT中,会使用其他模型,这些模型具有不 同的术语、概念、规则和UBIQUITOUS LANGUAGE的技术行话。通过划定明确的边界,可以使模型保持纯粹,因而在它所适用的CONTEXT中更有效。同时,也避免了将注意力切换到其他CONTEXT时引起的混淆。跨边界的集成必然需要进行一些转换,但我们可以清楚地分析这些转换。
将不同模型的元素组合到一起可能会引发两类问题:重复的概念和假同源。
CONTINUOUS INTEGRATION 持续集成
当很多人在同一个BOUNDED CONTEXT中工作时,模型很容易发生分裂。团队越大,问题就 越大,但即使是3、4个人的团队也有可能会遇到严重的问题。然而,如果将系统分解为更小的 CONTEXT,最终又难以保持集成度和一致性。
CONTINUOUS INTEGRATION是指把一个上下文中的所有工作足够频繁地合并到一起,并使它们 保持一致,以便当模型发生分裂时,可以迅速发现并纠正问题。
像领域驱动设计中的其他方法一 样,CONTINUOUS INTEGRATION也有两个级别的操作:(1) 模型概念的集成;(2) 实现的集成。
建立一个把所有代码和其他实现工件频繁地合并到一起的过程,并通过自动化测试来快速查 明模型的分裂问题。严格坚持使用UBIQUITOUS LANGUAGE,以便在不同人的头脑中演变出不同的 概念时,使所有人对模型都能达成一个共识。
CONTEXT MAP 上下文映射图
通过定义不同上下文之间的关系,并在项目中创建一个所有模型上下文的全局视图, 可以减少混乱。
CONTEXT MAP位于项目管理和软件设计的重叠部分。按照常规,人们往往按团队组织的轮廓来划定边界。紧密协作的人会很自然地共享一个模型上下文。办公室的物理位臵也有影响。
识别在项目中起作用的每个模型,并定义其BOUNDED CONTEXT。这包括非面向对象子系统 的隐含模型。为每个BOUNDED CONTEXT命名,并把名称添加到UBIQUITOUS LANGUAGE中。
描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容。
先将当前的情况描绘出来。以后再做改变。
CONTEXT MAP无需拘泥于任何特定的文档格式。
BOUNDED CONTEXT之间的关系
SHARED KERNEL 共享内核
从领域模型中选出两个团队都同意共享的一个子集。当然,除了这个模型子集以外,还包括 与该模型部分相关的代码子集,或数据库设计的子集。这部分明确共享的内容具有特殊的地位, 一个团队在没与另一个团队商量的情况下不应擅自更改它。
CUSTOMER/SUPPLIER DEVELOPMENT TEAM 客户/供应商开发团队
在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团 队的客户。根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道 双方的约定和进度。
在迭代期间,下游团队成员应该像传统的客户一样随时回答上游团队的提问,并帮助解决问题。
CONFORMIST 跟随者
当两个具有上游/下游关系的团队不归同一个管理者指挥时,CUSTOMER/SUPPLIER TEAM这样 的合作模式就不会奏效。勉强应用这种模式会给下游团队带来麻烦。
通过严格遵从上游团队的模型,可以消除在BOUNDED CONTEXT之间进行转换的复杂性。尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择CONFORMITY 模式可以极大地简化集成。此外,这样还可以与供应商团队共享UBIQUITOUS LANGUAGE。供应商 处于统治地位,因此最好使沟通变容易。他们从利他主义的角度出发,会与你分享信息。
ANTICORRUPTION LAYER 防腐层
创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。这个层通过另一个 系统现有接口与其进行对话,而只需对那个系统作出很少的修改,甚至无需修改。在内部,这个层在两个模型之间进行必要的双向转换。
这种连接两个系统的机制可能会使我们想到把数据从一个程序传输到另一个程序,或者从一 个服务器传输到另一个服务器。我们很快就会讨论技术通信机制的使用。但这些细节问题不应与 ANTICORRUPTION LAYER混淆,因为
ANTICORRUPTION LAYER并不是向另一个系统发送消息的机制。 相反,它是在不同的模型和协议之间转换概念对象和操作的机制。
SEPARATE WAY 各行其道
集成总是代价高昂,而有时获益却很小,因此在各自BOUNDED CONTEXT中自行开发,互相之间不进行任何关联和交互,是最简单有效的方式。
OPEN HOST SERVICE 开放主机服务
当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。需要维护的东西会越来越多,而且进行修改的时候担心的事情也会越来越多。
定义一个协议,把你的子系统作为一组SERVICE供其他系统访问。开放这个协议,以便所有 需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个 别团队的特殊需求除外。满足这种特殊需求的方法是使用一次性的转换器来扩充协议,以便使共享协议简单且内聚。
OPEN HOST SERVICE使用一个标准化的协议来支持多方集成。它使用一个领域模型来在各系 统间进行交换,尽管这些系统的内部可能并不使用该模型。
PUBLISHED LANGUAGE 公开发布的语言
两个BOUNDED CONTEXT之间的模型转换需要一种公共的语言。 把一个良好文档化的、能够表达出所需领域信息的共享语言作为公共的通信媒介,必要时在 其他信息与该语言之间进行转换。
BOUNDED CONTEXT之间关系的权衡
不同的context关系对其他系统的控制和团队沟通能有不同的要求,在选择时需要仔细权衡。
对于较大模型,这两者的要求均较高,但大模型能使团队共享语言更全面,沟通更清楚。
精炼
精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中提取出最重要的内容, 而这种形式将使它更有价值,也更有用,精炼可以帮助我们把注意力集中于核心领域,精炼的主要目的是为核心域减负。
CORE DOMAIN 核心域
对模型进行提炼。找到CORE DOMAIN并提供一种易于区分的方法把它与那些起辅助作用的模型和代码分开。最有价值和最专业的概念要轮廓分明。尽量压缩CORE DOMAIN。 让最有才能的人来开发CORE DOMAIN,并据此要求进行相应的招聘。在CORE DOMAIN中努力开发能够确保实现系统蓝图的深层模型和柔性设计。
GENERIC SUBDOMAIN 通用子域
识别出那些与项目意图无关的内聚子领域。把这些子领域的通用模型提取出来,并放到单独 的MODULE中。任何专有的东西都不应放在这些模块中。
把它们分离出来以后,在继续开发的过程中,它们的优先级应低于CORE DOMAIN的优先级, 并且不要分派核心开发人员来完成这些任务(因为他们很少能够从这些任务中获得领域知识)。
此外,还可以考虑为这些GENERIC SUBDOMAIN使用现成的解决方案或“公开发布的模型” (PUBLISHED MODEL)。
当开发这样的软件包时,有以下几种选择:
选择1:现成的解决方案 购买或使用开源
选择2:公开发布的设计或模型
选择3:把实现外包出去
选择4:内部实现
DOMAIN VISION STATEMENT
写一份CORE DOMAIN的简短描述(大约一页纸)以及它将会创造的价值,也就是“价值主张”。 那些不能将你的领域模型与其他领域模型区分开的方面就不要写了,展示出领域模型是如何实现。这份描述要尽量精简。尽早把它写出来,随着新的理解随时修改它。 DOMAIN VISION STATEMENT可以用作一个指南,它帮助开发团队在精炼模型和代码的过程中 保持统一的方向。
HIGHLIGHTED CORE
1.编写一个非常简短的文档(3~7页,每页内容不必太多),用于描述CORE DOMAIN以及CORE 元素之间的主要交互过程。
2.把模型的主要存储库中的CORE DOMAIN标记出来,不用特意去阐明其角色。使开发人员很容 易就知道什么在核心内,什么在核心外。
3.把精炼文档作为过程工具
COHESIVE MECHANISM 内聚机制
把概念上的COHESIVE MECHANISM(内聚机制)分离到一个单独的轻量级框架中。要特别注意公式或那些有完备文档的算法。用一个INTENTION-REVEALING INTERFACE来暴露这个框架的功能。现在,领域中的其他元素就可以只专注于如何表达问题(做什么)了,而把解决方案的复杂 细节(如何做)转移给了框架。
SEGREGATED CORE 分离的核心
对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来, 并增强CORE的内聚性,同时减少它与其他代码的耦合。把所有通用元素或支持性元素提取到其他对象中,并把这些对象放到其他的包中——即使这会把一些紧密耦合的元素分开。
此模式是一种重构核心的方法,与通用子域是相反的方式,分离核心是将最核心的领域内容在当前模型分离出去,形成更内聚的核心模型。
ABSTRACT CORE
把模型中最基本的概念识别出来,并分离到不同的类、抽象类或接口中。设计这个抽象模型, 使之能够表达出重要组件之间的大部分交互。把这个完整的抽象模型放到它自己的MODULE中, 而专用的、详细的实现类则留在由子领域定义的MODULE中。
ABSTRACT CORE(抽象核心)提供了主要概念及其交互的简化视图。
可以结合PLUGGABLE COMPONENT FRAMEWORK进行理解,高度抽象出核心模型,具体实现在其他子领域中,不同子领域只依赖抽象核心,具体子领域通过依赖注入方式调用,使子领域之间解耦合,也让核心域内容更清晰。
大型结构
在一个大的系统中,如果因为缺少一种全局性的原则而使人们无法根据元素在模式(这些模 式被应用于整个设计)中的角色来解释这些元素,那么开发人员就会陷入“只见树木,不见森林” 的境地。
我们需要理解各个部分在整体中的角色,而不必去深究细节。 “大型结构”是一种语言,人们可以用它来从大局上讨论和理解系统。
从总体上讲,大型结构并不是必须要用的。
EVOLVING ORDER 演变的顺序
让这种概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。 不要依此过分限制详细的设计和模型决策,这些决策和模型决策必须在掌握了详细知识之后才能确定。
可以理解为大模型需要随着应用程序的改变而改变。
SYSTEM METAPHOR 系统隐喻
软件设计往往非常抽象且难于掌握。开发人员和用户都需要一些切实可行的方式来理解系 统,并共享系统的一个整体视图。
SYSTEM METAPHOR(系统隐喻)是一种松散的、易于理解的大型结构,它与对象范式是协调的。 由于系统隐喻只是对领域的一种类比,因此不同模型可以用近似的方式来与它关联,这使得人们能 够在多个BOUNDED CONTEXT中使用系统隐喻,从而有助于协调各个BOUNDED CONTEXT之间的工作。
举例:防火墙
RESPONSIBILITY LAYER 责任分层
如果每个对象的职责都是人为分配的,将没有统一的指导原则和一致性,也无法把领域作为 一个整体来处理。为了保持大模型的一致,有必要在职责分配上实施一定的结构化控制。
分层模式有一种变体最适合按职责来分层,我们把这种变体称为RELAXED LAYERED SYSTEM(松散分层系统) , 如果采用这种分层模式,某一层中的 组件可以访问任何比它低的层,而不限于只能访问直接与它相邻的下一层。
注意观察模型中的概念依赖性,以及领域中不同部分的变化频率和变化的原因。如果在领域 中发现了自然的层次结构,就把它们转换为宽泛的抽象职责。这些职责应该描述系统的高层目的 和设计。对模型进行重构,使得每个领域对象、AGGREGATE和MODULE的职责都清晰地位于一个 职责层当中。
KNOWLEDGE LEVEL
当我们需要让用户对模型的一部分有所控制,而模型又必须满足更大的一组规则时,可以利用KNOWLEDGE LEVEL(知识级别)来处理这种情况。 它可以使软件具有可配置的行为,其中实体中的角色和关系必须在安装时(甚至在运行时)进行修改。
如果在一个应用程序中,ENTITY的角色和它们之间的关系在不同的情况下有很大变化,那么复杂性会显著增加。在这种情况下,无论是一般的模型还是高度定制的模型,都无法满足用户的需求。为了兼顾各种不同的情形,对象需要引用其他的类型,或者需要具备一些在不同情况下包括不同使用方式的属性。具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为 了满足不同的组装规则。
创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分为两个“级别”,一个是非常具体的级别,另一个级别则提供了一些可供用户或超级用户定制的规则和知识。
PLUGGABLE COMPONENT FRAMEWORK 可插入式组件框架
从接口和交互中提炼出一个ABSTRACT CORE,并创建一个框架,这个框架要允许这些接口的各种不同实现被自由替换。同样,无论是什么应用程序,只要它严格地通过ABSTRACT CORE的接 口进行操作,那么就可以允许它使用这些组件。
一个缺点是它是一种非常难以使用的 模式。它需要高精度的接口设计和一个非常深入的模型,以便把一些必要的行为捕获到 ABSTRACT CORE中。
总结
毫无疑问,软件的核心是业务,但在软件行业的发展历程中,大部分人的精力是投入到一些基础组件的研发当中, 领域驱动设计是几乎是唯一对于领域复杂性进行研究的理论。
在本次通读书籍后,想将DDD归纳为:面向对象设计+极限编程的理念来应对应对领域复杂性,其理论的核心内容就是面向对象设计的模型贯穿始终,以及在设计中始终贯彻将模型表达的完整、清晰、简单。如此概况可能也不甚准确,但此理论中提到的相关模式确实可以在系统设计过程中帮助解决一些问题。
结合现状来看,领域驱动设计的落地是非常困难的,很难将书中提到所有模式逐一使用,但判断是否为DDD的标志也并不是如此,最标志性的特征是把“理解目标领域并将学到的知识融合到软件中”当作首要任务。
最后,此篇是我对《领域驱动设计 软件核心复杂性应对之道》的读书心得,与各位共勉,创建好的软件是一项需要学习和思考的活动。
- 点赞
- 收藏
- 关注作者
评论(0)