vlambda博客
学习文章列表

《领域驱动设计》读书笔记

最近在看DDD的经典的一本书《领域驱动设计-软件核心复杂性应对之道》,正好也在做一个权限模块的设计,然后尝试着用DDD来做业务的梳理。

DDD在软件行业应用很广泛,技术大会上也经常有嘉宾来分享各自的实践。从业务梳理,架构设计,开发,重构,整个软件开发过程,都有重要的指导意义。

全书介绍了领域驱动设计的系统化方法,还有作者本身的见解及经验,展现了一些可扩展的设计最佳实践、已验证过的技术以及便于应对复杂领域的软件项目开发的基本原则。但是因为翻译和例子(举了航海及金融的业务,不好理解)不太好,导致看的过程会觉得比较晦涩。整理笔记的时候,摘抄了书中一些核心内容,也加入一些自己的理解。

全书分成了四个部分:

  • 运用领域模型:介绍相关的概念及作用

  • 模型驱动设计的构造块:实现模型驱动设计

  • 通过重构来加深理解:重构对领域模型的影响

  • 战略设计:从战术上升到战略层设计

运用领域模型


这部分主要讲领域模型相关的概念,模型在领域设计中的作用。

模型在领域驱动设计中的作用:

  • 模型和设计的核心互相影响,可以基于模型的理解来解释代码

  • 模型是团队所有成员所使用的通用语言的中枢,可以借助自然语言对模型进行精化

  • 模型是浓缩的知识,对业务有足够的抽象和提炼

软件的核心是为用户解决领域相关的问题的能力。技术不能脱离业务。

这个部分介绍了四章:

  • 消化知识:需要理解业务,并且持续理解

  • 交流和语言的应用:统一团队的语言,沟通和文档的作用

  • 绑定模型与实现:建模和实现过程需要注意事项

消化知识

消化知识即理解业务。

有效建模的因素有以下:

  • 模型和实现绑定

  • 建立了一种基于模型的语言

  • 开发了一个蕴含丰富知识的模型

  • 提炼模型

  • 头脑风暴和实验

我们在权限模块的时候也有类似的活动,如头脑风暴。核心人员做在一起来讨论整个系统的核心业务,统一大家对权限的认识,基于UML图来画领域模型,把技术细节忽略掉,专注于核心的业务。

交流与语言的应用

这章主要讲的是技术人员要与领域专家保持沟通,提到了需要注意下面几点:

一个团队,一个语言(统一团队内部的语言,对各个实体的理解及表达是否一致):

  • 领域专家和开发通过讨论可以发现模型中不足,模型的精确性也会促使领域专家发现想法的矛盾之处。

  • 开发和领域专家可以通过模型走查场景

文档与图:

  • 图只包含对象模型的重要概念,应该避免包罗万象的对象模型图。

  • 设计的重要细节应该在代码中体现出来。

  • 图是一种手段,可以促进头脑风暴。

  • 模型不是图,图的目的是帮助表达和解释模型

书面设计文档:

  • 文档应作为代码和口头交流的补充

  • 代码作为设计文档也有局限性,会淹没在各种细节中

  • 文档不应该重复表示代码已经明确表达出的内容

  • 文档应该鲜活并保持最新

  • 文档必须深入到各个项目中去

对于文档和图,我们在做的时候觉得是应该有文档的,前期设计时,我们大部分的时候讨论都是基于我们画UML图来进行沟通,来走查我们的业务场景。但是大部分开发人员都不爱写文档,所以我们做的时候也尽量精简文档。

绑定模型及实现*

这章介绍的是则是模型的实现过程可能会出现的一些问题。

两种错误的开发流程:

  • 建立了领域模型,但是不能直接帮助开发可运行的软件。

  • 没有设计,直接累加功能。

模型驱动设计不再将分析模型和程序设计分开,而是寻求一种能够满这两方面需求的单一模型。

软件系统各个部分的设计应该忠实地反映领域模型,以便体现出这二者之间的明确对应关系。我们应该反复检查并修改模型,以便软件可以更加自然地实现模型,即使想让模型反映出更深层次的领域概念也应如此。

从模型中获取用于程序设计和基本职责分配的术语。让程序代码成为模型的表达,代码的改变可能会是模型的改变。

完全依赖模型的实现通常需要支持建模范式的软件开发工具和语言,如面向对象的编程。

设计了领域模型却没有派上用场的原因:

  • 模型的一些意图在其传递过程中丢失了。

  • 模型和程序实现及技术互相影响,而我无法获取这种反馈。

任何参与建模的技术人员,都需要花时间了解代码。任何负责修改代码的人则必须学会用代码来表达模型。

我们之前在做的时候也发生过类似的,前期系统设计一完成,领域模型便没有人关注了,导致后面我们代码实现,跟我们原来设计的领域差异还是比较大的,所以后面需要持续关注代码的实现。

模型驱动设计的构造块


这部分主要讲的是如何来设计领域模型,细微的模型差别和设计决策是如何影响领域驱动设计过程的。

内容包括如下部分:

  • 分离领域:如何做架构设计

  • 各个模型对象的区分:区分实体,值对象,service等

  • 领域对象的生命周期:怎么创建?怎么跟踪?涉及到工厂,仓库等概念。

分离领域

对于架构设计来说,建议把领域模型对应的各个领域对象集中在一层,跟技术框架无关。

分层架构模式:

  • 用户层:负责向用户显示信息和解释用户指令。

  • 应用层:定义软件要完成的任务,并且指挥表达领域概念对象来解决问题。这一层也是与其它系统的应用层进行交互的必要通道。应用层要尽量简单,不包含业务规则或知识,而只为下一层的领域对象协调任务,分配工作,使它们互相协作。

  • 领域层(模型层):负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的。但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心。

  • 基础设施层:为上面各层提供通用的技术能力。

将所有与领域模型相关的代码放在一个层中,把它跟其它各层分开。领域对象应该将重点放在如何表达领域模型上,而不需要考虑自己的显示和存储问题,也无需管理应用任务等内容。

除了分层架构,这里还介绍反模式,就是不做分层,在用户界面里实现所有的逻辑,对于某些场景来看,也有它的优势。

软件中所表示的模型

本章主要讨论如何设计和简化关联开始,然后讲了3个模型元素:Entity, Value Object, Service和Module。

简化关系的方法:

  • 规定一个遍历方向:如果不是双向的关联,可以按一个遍历方向进行。

  • 添加一个限定符,以便有效减少多重关联:如一个国家某个时期只能有一个总统,这样可以把多重关系简化成一对一关系。

  • 消除不必要的关联

Entity:主要由标识定义的对象(有唯一的ID来标识它,通过这个ID可以查找到所有的信息, 如人有身份证号),可以是任何事物,只需要满足以下条件:

  • 它在整个生命周期中具有连续性

  • 它的区别并不是由那些对用户非常重要的属性决定。

ValueObject: 如果我们只关心一个模型元素的属性时,这个就是ValueObject。如我用铅笔,具体是哪支铅笔我并不关心。

Service: 以对象形式存在,但是除了执行一些操作之外没有其它意义,如一些manager等类。好的service有3个特征:

  • 与领域概念相关的操作并不是Entity和ValueObject的一个自然组成部分

  • 接口是根据领域模型的其它元素定义的

  • 操作是无状态的。

Module是更粗粒度的划分,Module之间是低耦合,内部是高内聚。

Module提供了两种观察模型的方式:

  • 可以在module中查看细节,而不会被整个模型淹没

  • 观察Module之间的关系,而不考虑其内部细节。

领域对象的生命周期

生命周期的管理的主要挑战有两个:

  • 在整个生命周期中维护完整性

  • 防止模型陷入管理生命周期复杂性造成的困境中

主要介绍了三个内容:

  • 聚合:通过定义清晰的所属关系和边界,并避免混乱,错综复杂的对象关系网来实现模型的内聚。

  • 使用工厂来创建和重建复杂对象和聚合,从而封装它们的内部结构

  • 生命周期的中间和末尾使用了仓库来提供查找和检索持久化对象并封装庞大基础设施的手段。

通过重构来加深理解


重构大家都能理解,我们在做重构的时候是否也会考虑领域的设计?之前没有觉得,看完后觉得,除非是做纯技术性的重构不需要考虑领域模型之外,涉及到的业务的重构一定要跟领域模型一起进行考虑。

要想成功开发出实用的模型,需要注意以下3点:

  • 复杂巧妙的领域模型是可以实现的,也是值得我们去花费力气实现的。

  • 这样的模型不能离开不断的重构,重构需要领域专家和热爱学习领域知识的开发人员参与进来。

  • 要实现并有效运用模型,需要精通设计技巧。

内容包括:

  • 突破:多思考,会找着重构的突破点

  • 将隐式概念转变成显式概念:把隐含的关系在领域模型中显式表现出来。

  • 柔性设计:如何让系统达到柔性设计。

  • 应用分析模式

  • 将设计模式应用于模型

  • 通过重构得到更深层的理解

突破

随着对业务的持续梳理和理解,就能发现深层模型,更符合当前的业务核心,实现突破。

举了银行信贷的例子来解释突破。

  • 华而不实的模型

  • 突破

  • 提炼深层的模型

  • 冷静决策,决定重构

关注根本:

  • 不要试图制造突破

  • 要为突破做好准备

  • 不要犹豫不去做小的改进

将隐式概念转变成显式概念

如何实现深层模型?深层模型之所以强大是因为它包含了领域的核心概念和对象,能够以简单灵活的方式表达出基本的用户活动,问题及解决方案。

深层建模的第一步就是设法在模型中表达出领域的基本概念,随后,在不断消化知识和重构的过程中,实现模型精细化。但是实际上这个过程是从我们识别出某个重要概念并且在模型和设计中把它显式表达出来的那一刻起开始的。

概念挖掘:

  • 倾听语言:看领域专家有没有一些术语能够简洁地表达出复杂的概念

  • 检查不足之处

  • 思考矛盾之处

  • 尝试,再尝试

如何为不太明显的概念建模?

  • 显示的约束:用策略模型来显式约束

  • 将过程建模为领域对象:将规格变成领域对象

  • 规模模式:

  • 规格的应用和实现:按下面的场景分别举了场景的示例

柔性设计

软件的困境:

  • 具有复杂行为的软件缺乏良好的设计时,重构或元素组合会变得很困难。

  • 一旦开发人员不能十分肯定地预知计算的全部含义,就会出现重复

  • 当设计元素都是整块的而无法重新组合的时候,重复就是一种必然的结果

为了使项目能够随着开发工作的进行加速前进,而不会由于它自己的老化停滞不前,设计必须让人们乐于使用,而且易于做出修改,这就是柔性设计。

为了实现柔性设计,有以下几个方法:

  • 释意接口:在命名类和操作时要描述它们的效果和目的,不要暴露它的实现。

  • SIDE-EFFECT-FREE-FUNCTION:尽可能把程序的逻辑放在函数中,因为函数是只返回结果而不产生副作用。

  • 断言:把可能错误的结果用断言显示写出来。

  • Conceptual Contour(概念轮廓):把设计元素(操作,接口和类等)分解为内聚的单元。

  • Standalone class:尽可能保持低耦合,把其它所有的无关概念提取到对象之外,类变得独立。

  • Closure of operation(闭合操作):定义操作时,让它的返回类型和其参数类型相同。

  • 声明式设计:通过声明式的设计,如反射,代码自动生成等技术手段来实现功能。

应用分析模式

之前的经验可以借鉴,类似于通用权限模型RBAC。

分析模式是一种概念集合,用来表示业务建模中的常见结构。它可能只与一个领域有关,也可能跨越多个领域。

分析模式是很有价值的知识

这块想起之前看过的《彩色uml建模》那本书,里面介绍了企业软件里常见的业务及相应的设计,如果贴合度高的话,可以拿过来套用。

将设计模式应用于模型

有些设计模式是可以用于领域模型的,有些却不能,如享元模式。

  • 策略模式

我们需要把过程中的易变部分提取到模型的一个单独的策略对象中。将规则 与它所控制的行为分开。按照策略设计模式来实现规则或可替换的过程。策略对象的多个版本完成过程的不同方式。

  • 组合模式

将对象组织为树来表示部分整体的层次结构。利用组合,客户可以对单独的对象和对象组合进行同样的处理。

定义一个反组合的所有成员都包含在内的抽象 类型。在容器上实现那些查询信息的方法时,这些方法返回由容器内容所汇总的信息。而叶节点则基于它们自己的值来实现这些方法,客户只需要全用抽象 类型,而无需区分 叶和容器。

通过重构得到更深层的理解

得到更深层理解的过程需要注意下面三点:

  • 以领域为本

  • 用一种不同的方式来看待事物

  • 始终坚持与领域专家对话

确定是否要开始重构?

  • 解决代码中的问题

  • 模型语言和专家没有保持一致,即使是代码看上去很整洁

  • 通过学习获得了更深刻的理解

  • 探索团队

如何保证重构领域的效率?

  • 自由决定:随时组建临时团队来研究某个问题。

  • 注意范围和休息:时间不能过长,范围不能过大。

  • 使用统一语言

借鉴先前的经验

  • 从书和领域自身的其它知识来源获得思路

  • 从分析模式汲取他人经验

  • 关注设计模式

  • 常见的形式体系(算术逻辑或谓词逻辑)

针对开发人员设计,领域模型设计也要考虑开发人员,更方便开发人员开发。

当发生以下情况时,就应该重构了。

  • 设计没有表达出团队对领域的最新理解

  • 重要的概念被隐藏在设计中了

  • 发现一个能令某个重要的设计部分变得更灵活的机会。

战略设计


之前介绍的都是战术设计,这一章主要是战略上的设计,是更上一层次的设计了。

随着系统的增长,它会变得越来越复杂,当我们无法通过分析对象来理解系统的时候,就需要掌握一些操纵和理解大模型的技术了。

战略设计原则必须把模型的重点放在捕获系统的概念核心,也就是系统的远景上。而且在完成这些目标的同时又不能为项目带来麻烦。为了帮助实现这些目标,这一部分探索了3个大的主题:

  • 上下文:划分系统边界,限界上下文。不同系统的领域模型不一样。

  • 精炼:减少混乱,把注意力集中到正确的地方。

  • 大型结构:用于描述整个系统的。

保持模型的完整性

大型系统领域模型的完全统一是不可行的也不划算。

放在一个模型下面一定要考虑下面的风险:

  • 一次尝试对遗留系统做过多的替换

  • 大项目可能会陷入困境,因为协调的开销太大,超出了这些项目的能力范围

  • 具有特殊需求的应用程序可能不得不使用无法充分满足需求的模型,而只能将这些无法满足的行为放到其它地方

  • 另一方面,试图用一个模型来满足所有人的需求可能会导致模型中包含过于复杂的选择,因而很难使用。

为了解决这些问题,则需要如下概念:

  • 限界上下文

  • 持续集成

  • 上下文地图

选择你的模型上下文策略的指导原则:

  • 团队决策或更高层决策

  • 置身上下文中

  • 转换边界:选择较大边界及较小边界所带来的优缺点。

  • 接受那些我们无法更改的事物:描述外部系统。

  • 与外部系统的关系

  • 设计中的系统

  • 用不同模型满足特殊需要

  • 部署

  • 权衡

精炼

精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中最重要的内容,而这些形式将使它更有价值,也更有用。模型就是知识的精炼。

领域模型的战略精练包括以下部分:

  • 帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作

  • 找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通

  • 指导重构

  • 专注于模型中最有价值的那部分

  • 指导外包,现成组件的使用以及任务委派

会涉及到下面几个概念:

  • Core Domain:划分优先级,优先关注核心模型,次要细节先忽略

  • Generic Subdomain:把子模型提出出来,放在单独的module.

  • Domain Vision Statement: 有一份核心模型的简短描述,即价值主张。尽早写下来,随时修改它。

  • Highlighted core:识别核心领域模型及对象间的关系。

  • Cohesive Mechanism(内聚机制):领域中核心元素专注于表达问题,解决方案的复杂细节转移给了框架。

  • Segregated Core:对模型进行重构,把核心概念从支持性元素中分离出来,增加core的内聚,同时减少它与其它代码的耦合。

  • abstract core:把模型中最基本的概念识别出来,并分离到不同的类,抽象类和接口中。通过抽象的模型来表达重要组件及业务。

大型结构

从更高层次来讲解架构

大型结构是一种语言,人们可以用它来从大局上讨论和理解系统。

设计一种应用于整个系统的规则模式,使人们可以通过它在一定程度上了解各个部分在整体中所处的位置。

一个没有任何规则的随意设计会产生一些无法理解整体含义且很难维护的系统。但早期的设计假设又会使项目束手束脚,而且会极大地限制应用程序中某些特定部分的开发人员,设计人员的能力。很快,开发人员就会为了适应结构而不得不在应用程序的开发上委曲求全,要么就是完全推翻架构而又回到没有协调的开发老路上。

让这种概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。不要依此过分限制详细的设计和模型决策。

当发现一种大型结构可以明显使系统变得清晰,而又没有对模型开发施加一些不自然的约束时,就应该采用这种结构。使用不合适的结构还不如不使用它。因此最好不要为了追求设计的完整性而勉强去使用一种结构。

当系统一个具体类比正好符合团队成员对系统的想象,并且能够引导它们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构,用它来组织设计,并把它吸收到统一语言中。系统隐喻应该既能促进系统的交流,又能指导系统的开发。它可以增加系统不同部分之间的一致性,甚至可以跨越不同的限界上下文,但所有的隐喻都不是完全精确的,因此就不断检查隐喻是否过度或不恰当,当发现它起妨碍作用时随时准备放弃它。

职责分层:如果每个对象的职责都是人为分配的,将没有统一的指导原则和一致性,也无法把领域作为一个整体来处理。为了保持大模型的一致,有必要在职责分配上实施一定的结构化控制。

需要考虑选择适当的层。

对层进行删除,合并,拆分和重新定义等操作时,应寻找并保留以下一些有用的特征。

  • 场景描述

  • 概念依赖性

当我们需要让用户对模型的一部分有所控制,而模型又必须满足更大一组规则时,可以利用知识级别来处理这种情况。它可以使软件具有可配置的行为,其中实体中的角色和关系必须在安装时进行修改。

通过重构得到更适当的结构:

  • 最小化

  • 沟通和自律

  • 通过重构得到柔性设计

  • 通过精炼可以减轻负担

领域驱动设计的综合运用

战略设计的3个基本原则(上下文,精炼和大型结构)并不是可以互相代替的,而是互为补充,并且以多种方式进行互动。

制定战略设计决策的6个要点:

  • 决策必须传达到整个团队

  • 决策过程必须收集反馈意见

  • 计划必须允许演变

  • 架构团队不必把所有最好,最聪明的人员都吸收进来

  • 战略设计需要遵守简约和谦逊的原则

  • 对象的职责要专一,而开发人员应该是多面手