总体来说,领域驱动设计与极限编程思想一脉相承,注重避免过度设计+迭代重构,本文对领域驱动设计进行一番概览,帮助大家对DDD进行快速了解
对象范式
对象范式流行的原因是在简单性和复杂性之间实现了一个很好的平衡,大部分人能比较容易理解面向对象设计的基本知识
第一部分 让领域模型发挥作用
语言和交流和使用
领域模型是软件项目的公共语言的核心,模型是人们头脑中形成的与项目有关的概念集合,它用术语和关系反映了领域的深层含义。这些术语和相互关系提供了模型的语义,使模型和代码实现紧密绑定
要想创建一种灵活的、蕴含丰富知识的设计,需要一种通用的、共享的团队语言,这种语言应该是时刻进行检验的:限界上下文的名称、大比例结构的术语、本书中提到的模式
绑定模型和实现
绑定模型和程序设计是可行的,如果模型对程序的实现显得不太实用,必须重新设计它。
面向对象编程基于建模范式,为模型构造提供了实现方式 ,对象与其它对象相互联系,可以组织成类并通过消息传递来完成相应的行为。对象设计的突破在于用代码来描述模型中的概念
如果程序设计基于一个能够反映出用户和领域专门所关心的基本问题的模型,这种设计可以将其含义更明确地展示给用户。给他们更多机会挖掘软件的潜能,也能使软件的行为合乎情理、前后一致。
第二部分:模型驱动设计的构造块
将领域设计与软件系统中的其它关注点分离会使设计与模型之间的关系非常清晰,根据不同特征来定义模型元素则会使元素的意义更加鲜明。对每个元素使用已验证的模式有助于创建出更易于实现的模型。
分离领域
分层架构
领域模型是一系列概念的集合,领域层是领域模型及所有与其直接相关的设计元素的表现,它由业务逻辑的设计和实现组成。在DDD中,领域层的软件构造反映出了模型概念
软件中所表示的模型
- 关联:对象之间的关联使得建模与实现之间的交互更为复杂
- entity
- valueobject
- service
- module
领域对象的生命周期
- 聚合
- 具有复杂关联的模型中,保证对象更改的一致性。关键的做法是要找到一种对象间冲突少,而固定规则联系更紧密的模型
- 根entity具有全局标识,边界内的entity具有本地标识
- aggregate不能引用除根entity之外的任何内部对象
- 只有aggregate的根才能通过数据库查询获取到
- aggregate内部对象可以保持对其它聚合根的引用
- 删除操作必须一次删除aggregate边界之内的所有对象
- 当提交对aggregate边界内部的任何对象的修改时,整个aggregate中的所有固定规则都必须被满足
- 工厂
- 资源库
- 为关系数据库设计对象
第三部分:通过重构来加深理解
重构的目的是得到深层次的模型,这个模型能捕捉到领域专家们的微妙关注点,还可驱动切实可行的设计
隐式概念转变为显式概念
概念挖掘
- 倾听领域专家的语言,把专家用简洁术语表达出来的复杂概念 加入模型
- 检查设计中的不足之处,就是操作复杂且难于解释的地方
- 思考矛盾之处
- 查阅书籍,可以让你一开始就形成一致且深层的认识
如何为那些不太明显的概念建模
- 显式的约束和过程,策略模式
- specification:用于限制另一个对象的约束,是为特殊目的创建的谓词形式的显式的value object
- 应用于生成器的优点:生成器实现与接口分离、接口把规则显式地表示出来、接口更为灵活、接口便于测试
第10章 柔性设计
设计必须要让人乐于使用并易于做出修改,这就是柔性设计,柔性设计是对深层建模的补充
在将隐式概念显式化得到深层模型的过程中,我们究竟要达到一个什么样的设计呢?这个过程中应该进行哪些实验,这正是本章要讨论的内容
模式:intention-revealing interfaces
封装的价值在于 命名类和接口时,能够把执行的代码规则显式地表达出来
模式:side-effect-free function
操作分为两个类别:命令和查询
副作用代表意外的结果,任何对系统状态产生的影响都叫副作用
因此,尽可能把程序的逻辑放到函数中,因为函数只返回结果而不产生明显的副作用的操作,严格地把命令(引起明显的状态改变的方法)隔离到不返回领域信息、非常简单的操作中。当发现一个非常适合承担复杂逻辑职责的概念时,就可以把这个复杂逻辑移到VALUEOBJECT中 ,这样可以进一步控制副作用
模式:Assertion
使用side-effect-free function后实体中仍会留有一些有副作用的命令,使用这些entity的人必须了解使用这些命令的后果。在这种情况下,使用Assertion(断言)可以把副作用明确地表示出来,使它们更易于处理
assertion作为后置条件,描述了一个操作的副作用,也就是调用一个方法之后必然会发生的结果
模式:conceptual contour
把设计元素 (操作、接口、类和AGGREGATE)分解为内聚的单元,在这个过程中,你对领域中一切重要划分的直观认识也要考虑在内。在连续的重构过程中观察发生变化和保证稳定的规律性,并寻找能够解释这些变化模式的底层conceptual contour。使模型与领域中那些 一致的方面 (正是这些方面使得领域成为一个有用的知识体系)相匹配
模式:standalone class
低耦合是减少概念过载的最基本办法。孤立的类是低耦合的极致
模式:closure of operation
在适当的情况下,在定义操作时让它的返回类型与其参数的类型相同。如果实现者 (implementer)的状态在计算中会被用到,那么实现者实际上就是操作的一个参数,因此参数和返回值应该与实现者有相同的类型。这样的操作就是在该类型的实例集合中的闭合操作。闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖性
声明式设计
assertion无法覆盖的副作用正是声明式设计的部分动机
DSL
从模型属性的声明来生成可运行的程序是DDD的理想目标,但
- 声明式语言不足以表达一切所需的东西,它把软件束缚在一个由自动部分构成的框架之内,使软件很难扩展到这个框架之外
- 代码生成技术破坏了迭代循环,它把生成的代码拿到到手写代码中,使得重新生成的破坏作用变得很大
接口显式地定义生成器的输入,同时也可以用来验证输出,使得接口易于测试
所以声明式设计的最大价值是用一个范围非常窄的框架来自动处理设计中某个特别单调且易出错的方面,例如持久化和对象关系映射。最好的声明式设计能够使开发人员不必去做那些单调乏味的工作,同时又完全不限制他们的自由
声明式设计风格
一旦设计中有了intenting-revealing interface,side-effect-free function和assertion,那就具备了使用声明式设计的条件(为什么呢?使用的specification的例子+)
切入问题的角度
怎么把这些模式结合起来,用于处理更大的设计呢
- 分割子领域
- 无法一下子处理好整个设计,需要一步步进行,从系统的某些方面看出适合用哪种方法处理,就把它提取出来加以处理。比如某部分分离出专门的数学,如果应用程序实施了某些用来限制状态改变的复杂规则,那么可以把这部分提取到一个单独的模型中,或者提取到一个允许声明规则的简单框架中
- 尽可能利用已有的形式
- 可以对你的领域或其他领域中那些 建立已久的概念系统加以修改和利用
第13章 将设计模式应用于模型
有些设计模式但并非所有模式都可用作领域模式,因为设计模式和领域模式有所区别。为了在领域驱动的设计中充分利用这些模式,我们必须同时从两个角度看待它们:从代码的角度来看它们是技术设计模式,从模型的角度来看它们就是概念模式
策略模式
我们需要把过程中的易变部分提取到模型的一个单独的 “策略”对象中。将规则与它所控制 的行为区分开。按照STRATEGY设计模式来实现规则或可替换的过程。策略对象的多个版本表示 了完成过程的不同方式
传统上,人们把STRATEGY模式看作一种设计模式,这种观点的侧重点是它替换不同算法的能力。而把它看作领域模型的侧重点是其表示概念的能力,这里的概念通常是指过程或策略规则
组合模式
当在领域中应用任何 一种设计模式时,首先关注的问题应该是模式思想是否确实适合领域概念。以递归方式在一些相关对象中导航确实比较方便,但它们是否真的存在整体-部分层次结构?你是否发现可以通过某种抽象方式把所有部分都归到同 一概念类型中?如果你确实发现 了这种 抽象方式,那么使用COMPOSITE可以令模型的这些部分变得更清晰,同时使你能够深入细致地考 虑设计模式的设计和实现问题。
第四部分:战略设计
战略设计原则必须指导设计决策,以便减少各个部分之间的互相依赖,并提高清晰度,而又不丢失关键的互操作性和协同性。战略设 计原则必须把模型的重点放在捕获系统的概念核心,也就是系统的“远景” 上。而且在完成这些目标的同时又不能为项目带来麻烦
大比例结构能够保持 各个不同部分之间的 一致性,从而有助于这些部分的集成。结构和精炼能够帮助我们理解各个部 分之间的复杂关系,同时保持整体视图的清晰。BOUNDEDCONTEXT使我们能够在不同的部分中 进行工作,而不会破坏模型或是无意间导致模型的分裂。把这些概念加进团队的 UBl QUrTous LANGUAGE中,可以帮助开发人员设计出他们自己的解决方案
保持模型的完整性
大型系统领域模型的完全统一是不可行的,也不是一种经济有效的做法。本章将介绍 一些用于识别、沟通和选择模型边界及关系的技术
bounded context(限界上下文)定义了每个模型的应用范围,而context map(上下文图)则给出了项目上下文以及它们之间关系的总体视图。这种清晰的视图能够使项目更好地进行,但仅仅有CONTEXTMAP是不够的。一旦有了BOUNDEDCONTEXT之后,就需要一种持续集成的过程,它能够帮助确保模型的统一。
模式:bounded context
任何一个大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会 出现bug .变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型 不应该在哪个上下文中使用
一个模型只在一个上下文中使用。这个上下文可以是代码的一个特定部分,也可以是某个特定团队的工作。如果模型是在团队的一次头脑风暴会议中得到的,那么这个模型的上下文仅限于 那次会议的讨论。就拿本书中的例子来说,示例中所使用的模型的上下文就是那个示例所在的小 节以及任何相关的后续讨论。模型上下文是为了保证该模型中的术语具有特定意义而必须要应用 的一组条件
明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表 现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受 到边界之外问题的干扰和混淆
模式:持续集成 continuous integration
建立一个经常把所有代码和其他实现工件合并到一起的过程,并通过自动测试来快速查明模型的分裂问题。严格坚持使用UBIQUITOUSLANGUAGE,以便在不同人的头脑中演变出不同的概念 时,使所有人对模型都能达成一个共识
模式:context map
其他团队中的人员并不是十分清楚CONTEXT的边界,他们会不知不觉地做出一些更改,从而使边界变得模糊或者使互连变得复杂。当不同的上下文必须互相连接时,它们可能会互相重叠
识别每个模型在项目中的作用,并定义其BOUNDED CONTEXT。这包括非面向对象子系统的隐含模型。为每个BOUNDED CONTEXT命名,并把名称添加到UBIQUTOUS LANGUAGE中。 描述模型之间的接触点,明确每次交流所需的转换,并突出任何共享的内容。画出现有的范围。为稍后的转换做好准备
限界上下文之间的关系:
- 共享内核
- customer/supplier development team
- conformist
- 防腐层
- 实现防腐层:一种方法是把它实现为FACADE/ADAPTER这两种模式和转换器的组合,外加两个系统之间进行对话所需的通信和传输机制
- separate way
- open host service
- 定义一个协议,把你的子系统作为一组SERVICE供其他系统访问。开放这个协议,以便所有 需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。满足这种特殊需求的方法是使用一次性的转换器来扩充协议,以便使共享协议简单且内聚
- published language
精炼
精炼是把 -堆混杂在 一起的组件分开的过程,以便从中提取出最重要的内容,使得它更有价值,也更有用。模型就是知识的精炼。通过每次重构得到更深层的理解,我们把关键的领域知识和优先级提取出来
- core domain
- 精炼的逐步提升
- 一份简单的DOMAIN VISION STATMENT只需很少的投人,它传达了基本概念以及它们的价值。HIGHLIGHTED CORE可以改善沟通,并指导决策制定过程,这也只需对设计进行很少的改动甚至无需改动。
- 通用子域
- 模型中除了增加复杂性以外并没有捕捉或传递任何专门的知识的部分
- DOMAIN VISION STATMENT
- 写一份COREDOMAIN的简短描述以及它将会创造的价值(大约一页纸),也就是“价值主张” 。 那些不能将你的领域模型与其他领域模型区分开的方面就不要写了。展示出领域模型是如何实现 和均衡各方利益的。这份描述要尽量精简。尽早把它写出来,等到获得新的理解后再修改它
大比例结构
设计一种应用于整个系统的规则 (或角色和关系)模式,使人们可以通过它在一定程度上了 解各个部分在整体中所处的位置(即使是在不知道各个部分的详细职责的情况下)
- EVOLVING ORDER
- 当发现一种大比例结构 可以明显使系统变得更清晰,而又没有为模型开发施加一些不自然的约束时,就应该采用这种结构
- SYSTEM METAPHOR
- 一种松散的、易于理解的大比例结构,它与对象范式是协调的
- RESPONSIBILITY LAYER
- 如果每个对象的职责都是手工分配的,将没有统一的指导原则和一致性 ,也无法把领域作为 一个整体来处理。为了保持大模型的 一致,有必要在职责分配上实施一定的结构化控制
- KNOWLEDGE LEVEL
- PLUGGALBE COMPONENT FRAMEWORK