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

4,071 阅读28分钟

作者: Eric Evans

image.png

一句话总结

技术只有解决了现实世界中的问题才变得有意义,基于业务领域进行建模并且在技术中表达出来才能使整个工程具备更高的质量与扩展性。

注:DDD 的一些方法论借鉴了《分析模式》Fowler 1996

脑图

yuque_diagram.jpg

详情

前言

  • 领域驱动设计可以满足大型复杂软件的持续迭代需求并且可以很好地控制软件工程的复杂度
  • 一些概念:
    • XP:极限编程。极限编程成人设计决策的重要性,但是强烈反对预先设计。相反,它将相当大的精力投入到促进沟通和提高项目快速变更能力的工作中。有了这种反应能力后,开发人员就可以在项目的任何阶段利用“简单而管用的方法”,然后不断的进行重构,增量迭代
    • 瀑布方法:传统的软件开发流程,与极限编程对应
    • 亚历山大模式:奥地利维也纳的一名建筑师,以其设计理论和丰富的建筑设计作品而闻名于世。他认为:建筑的使用者比建筑师更清楚他们需要什么,他创造并实践验证了“模式语言”。代表作《建筑模式语言》对计算机领域的设计模式运动产生了深远的影响。

第一部分

运用领域模型

  • 一些概念
    • 模型:是一种简化,他是对现实的解释——把解决问题密切相关的地方抽象出来而忽略相关的细节。
    • 领域:每个软件都是为了执行用户的某项活动,或是满足用户的某种需求。这些用户应用软件的问题区域就是软件的领域
    • 领域专家:某个领域的工作者(产品经理),他们可能不懂软件设计但是精通所在的领域
    • 领域模型不是某种特殊的图(虽然大部分时间我们都用 UML 去表示),而是这种图所要传达的思想。是对领域知识有组织的抽象
  • 模型在领域驱动设计中的作用
    • 决定模型选择的三个基本意图
      • 模型与设计的核心相互影响
      • 模型是团队所有成员使用的通用语言的核心
      • 模型是浓缩的知识
  • 软件的核心:为用户解决领域相关问题的能能力,所有的其他特性不管多么的重要,都要服务于这个基本目的。(就像乔布斯说的有了用户需求才有技术的用武之地,先有需求,再有技术)

第1章:消化知识

不断与领域专家探讨,提炼领域的核心要点并通过非正式的图表现出来(建模&提炼模型)

  • 有效建模的要素
    • 模型和实现的绑定。最初的原型虽然简陋,但它在模型与实现之间建立了早期的链接并且后续的迭代一直维护这个链接
    • 建立了一种基于模型的语言:领域专家与软件工程师都明白模型中某个模块的具体含义并且可以互相讨论模型是否合理
    • 开发了一个蕴含丰富知识的模型:提炼的模型是领域核心问题的抽象
    • 提炼模型:最初的模型可能不完善,但是在后续与领域专家的讨论中重要的概念会被不断的补充到模型里面,不重要的概念会被剔除
    • 头脑风暴与实验:开发者与领域专家可以通过基于模型的语言不断的探讨各种场景,将团队的知识转化为有价值的模型
  • 知识消化
    • 知识消化不是一项孤立的活动,它一般是在开发人员的领导下,由开发人员与业务方组成的团队来共同协作。团队一起消化理解模型的过程中,成员之间的交互也会发生变化(开发人员不断精化学习重要的业务原理,领域专家被迫提炼自己知道的知识并且让这些概念更加严谨)。模型在不断改进的过程中也成为组织项目信息流的工具。
  • 持续学习
    • 随着团队成员的流失、重组,项目会丢失知识。代码与文档不会直观的表达出知识,一旦某些原因人们没有口头传递知识,那么知识就会丢失。高效率的团队需要有意识的积累知识并持续学习。即使早期的知识会丢失,早期的工作也是非常有必要的,因为早期的工作启动了知识消化过程,团队成员(开发者、业务方)开始使用一种公共的语言而且形成了贯穿整个实现过程的反馈闭环。
  • 知识丰富的设计
    • 领域的建模不只是寻找实体、对象。而是应该充分的吸取知识。很多领域问题是依赖领域专家的常识来解决的,这些常识是不能被软件所理解的,只有软件专家与领域专家紧密协作才能把规则、知识澄清并充实,消除规则之间的矛盾并删除一些无用规则。
    • 领域模型与相应的设计可以用来保护和共享知识
      • 程序员与业务专家都能理解一些问题是领域的重要业务规则而不是不起眼的计算(比如文章中举例子的“超定”业务规则)
      • 程序员可以想业务专家展示代码,领域专家可以在程序员的指导下看懂这些技术工件以便形成反馈闭环
  • 深层模型
    • 有用的模型很少停留在表面,随着对领域和应用程序需求的理解逐步加深,我们会抛弃一些看起来很重要的表面元素并添加一些新的元素。一些巧妙的抽象会渐渐的浮出水面,而这些往往切中问题的要害。
    • 知识消化是一种探索 ,永无止境(架构是演进的,不存在一开始就非常棒的架构)

第2章:交流与语言的使用

领域模型可以成为软件项目通用语言(业务方与开发者都能看得懂)的核心

  • 2.1 UBIQUITOUS LANGUAGE(通用语言)
    • 通用语言是指业务方与开发者都能看得懂的词或者短语,而不是任意一方的方言
    • 通用语言的好处:通过大量使用基于模型的语言并且不达到流畅不罢休,我们可以得到一个完整的易于理解的模型。它由简单元素构成,并且通过组合这些简单元素可以表达复杂的概念。注意:团队在日常沟通中应该坚持使用这种语言方便暴露模型的问题,画图、写东西特别是讲话的时候都要使用这种语言。
    • 通用语言的修改就是对模型的修改
    • 领域专家应该地址不合适或者无法充分表达领域理解的术语或者结构,开发人员应该密切关注那些会妨碍设计的有歧义或者不一致的地方
    • 通用语言是那些以非代码形式呈现的设计的主要载体(可以理解成通用语言是代码背后设计思想的载体)
  • 2.2 “大声地”建模
    • 人类是擅长处理口语的复杂性,在团队交谈的过程中应该使用可以体现模型的“单词”或者“短语”,这样可以将我们的语言能力用于建模工作。(PS:人的大脑很酣畅处理口语,具有不同语言背景的人放在一起交谈时,如果没有公共语言,他们会创造一种“混杂语”的公共语言,混杂语虽然不像讲话者母语那样详尽,但是它适合当前的任务。人们在交谈时自然会发现这些混杂语的单词与实际含义的差别,而后自然而然的解决这些差别)
    • 团队讨论系统时要结合模型,使用模型元素及交互大声地描述场景,并且按照模型允许的方式将各种概念结合在一起,找到更简单的表达方式讲出你要说的话,然后将这些新的想法应用到图和代码中。(尽量使用语言去讨论模型中的子模块,这样开发者与领域专家互相交谈中才能找到模型不合适的地方并且想发设法改进模型。最后开发者将改进后的模型应用到代码中使程序更合理
  • 2.3 一个团队,一种语言
    • 团队中各个角色必须使用 UBIQUITOUS LANGUAGE 进行交谈,这样领域专家、开发者、代码所表达的东西才是基于同同一种语言&同一个共享的模型
  • 2.4 文档和图
    • 尽量不要用详细的大图讨论问题
      • 有些人(大多数人)是视觉动物,图可以帮助人们掌握某些类型的信息。
      • 讨论某个问题时避免使用大而全的图,可以使用一张包含当前问题最关键的3-5个对象的小图,这样每个人都能集中注意力
      • 避免使用包罗万象的对象模型图,设计的重要细节应该体现在代码中。互为补充的图跟文档才能更吸引人们把注意力放在核心要点上
      • 模型不是图,图的目的只是帮助表达与理解模型,模型是图背后所表达的思想
    • 书面设计文档
      • 文档应该作为代码与口头交流的补充:文档只描述大尺度的结构就行了,细节看代码
      • 文档应该保持更新:把文档的数量降到最低就可以避免文档与项目脱节
    • 完全依赖代码去说明模型不靠谱:方法的名字可能与该方法实际做的事情不一致
    • 解释性模型:为了学习一些特定的领域引进的模型,该模型提供了上下文,可以为某个领域制定表达力更强的风格(不是所有领域的建模都必须要用对象模型,但是对象模型是经过软件工程实际检验的,大部分系统都是以对象模型为基础进行建模的)

第3章:绑定模型与实现

领域模型是要切实帮助软件工程的进展的,纸上谈兵没有任何意义

  • MODEL-DRIVEN DESIGN(模型驱动设计):不再将分析模型与程序设计分离,而是选择一可以满足分析与程序设计的单一模型。软件系统中的各个部分应该忠实地反应领域模型。完全依赖模型的实现通常需要支持建模范式的软件开发工具和语言,比如面向对象的语言。
  • 建模范式和工具支持:面向对象的程序设计是目前大多数项目所使用的的建模范式
  • 为什么模型对用户至关重要:举了 IE 浏览器的收藏夹功能失败的案例说明模型对用户也是非常重要的,用户了解模型后可以更顺畅的使用软件并且挖掘软件的潜能
  • HANDS-ON MODELER:切身实践的建模者。模型的创建者一定要躬身入一线写代码,不然项目的实施与模型的设计会脱节

第二部分:模型驱动设计的构造块

实现模型元素之间的关注点分离可以使模型更清晰且易维护

image.png

第4章:分离领域

  • 我们需要将领域对象与系统中的其他功能进行分离,这样就能避免领域概念与其他软件技术概念弄混淆
  • LAYERED ARCHITECTURE:分层架构。层的划分一定是要“高内聚、低耦合”。
    • 常用的系统分层
      • 用户界面层:UI层
      • 应用层:定义软件要完成的任务并且指挥领域层的对象来完成任务。比如算路服务系统中应用层就应该是“算 A 到 B 点的路径”,至于怎样算,需要放在领域层
      • 领域层:是业务软件的核心,负责表达业务概念、业务规则、业务状态信息
      • 基础设施层:基建
    • 将各层关联起来:层中的任意元素只能依赖本层的其他元素或者下层元素,下层元素不可以依赖上层元素,下层与上层通信需要走额外的机制(比如回调、消息等)
    • 架构框架:不要百分百的信任框架,框架的确可以提高开发效率;但是框架不是万能的。过分依赖框架会导致开发陷入泥潭。(比如早期的 J2EE 会把所有的领域对象实现为 实体bean 但是这会拖慢性能与开发效率)
  • 领域层是模型的精髓:领域层包含了领域模型及所有与其直接相关的概念的集合,所以将领域模型的实现独立出来才能不被繁杂的其他技术问题困扰
  • THE SMART-UI “反模式”:快速搭建用户界面,不考虑抽象与分层,直接将业务逻辑放在 UI 中。在某些场景下是非常适用的(比如一些简单的小项目),但是这对于 DDD 来说是 “反模式”。
    • 如果一个架构能把哪些与领域相关的代码隔离出来,得到一个内聚的领域设计,同时能使领域与系统的其他部分保持松散的耦合,那么这种架构就可以支持领域驱动设计

第5章:软件中所表示的模型

  • 基本的模型元素:
    • Entity:实体,具有连续性和标志的对象(比如人,5岁跟50岁是同一个对象)
    • Value Object: 与 Entity 相反,没有概念上的标识,只是描述了事务的某些特性。他应该是不可变的。
    • Service:一些动作,无状态的操作
    • Module(Package):包与包之间应该是低耦合的,包内部应该是高内聚的。Module的名字应该是 UBIQUITOUS LANGUAGE 的术语,Module 与其名字应该能够反映出领域的深层知识。一些相关联的概念应该被挪到同一个包中,值得牺牲一些分层的好处来换取更内聚的领域层
  • 关联:模型中每个可遍历的关联,在软件中都要有同样属性的机制。(尽量减少关联,只对那些与模型对象重要的关联,最大限度减少双向关联)
  • 尽量都使用面向对象的建模范式,这种范式已经足够强大了
    • 不要与建模范式对抗,我们可以换个角度来思考领域,找到适合范式的模型概念(如果与建模范式不匹配,那就是你的思考方式不对)
    • 把通用语言作为依靠的基础
    • 不要一味的依赖 UML
    • 保持怀疑态度:尽量不要使用混合范式(比如系统中既有面向对象的范式又有面向过程的范式,当然关系型范式(DB)可以很好的与面向对象的范式融合因为存储的就是对象)

第6章:领域对象的生命周期

  • AGGREGATE(聚合):把领域模型划分成一个一个的模块,模块与模块之间只能互相引用到根,
    • AGGREGATE 就是一组相关对象的集合,每个 AGGREGATE 有一个根(root Entrity)与边界(boundary),边界定义了 AGGREGATE 内部有什么。**外部对象只可以引用根,边界内部的对象可以相互引用。内部的对象可以引用别的 AGGREGATE 根, **
    • AGGREGATE 的一些概念:
      • 根 Entity 具有全局标志,他最终负责检查固定的规则
      • 根 Entity 具有全局唯一的标志,边界内的 Entity 只有 AGGREGATE 标志
      • AGGREGATE 外部的对象不能引用除根 Entity 外的任何内部对象,根 Entity 可以把内部 Entity 的临时引用传递出去,但是仅对这次调用有效。外部不能改。根 Entity 还可以把 Value Object 的值传递出去,外部同样不能改
      • 只有 AGGREGATE 的根才能通过数据库查询获取,其他的 AGGREGATE 内部对象只能通过遍历依赖关系找到
      • AGGREGATE 的内部对象可以保持对其他 AGGREGATE 根的引用
      • 删除操作必须以 AGGREGATE 为单位全部删除
      • 对 AGGREGATE 边界内部的任意对象进行修改时,必须保证 AGGREGATE 内的所有对象都得到更新(因为 AGGREGATE 内的对象是互相引用的)
    • 很多时候把 Entity 改成一个 Value Object 会大幅降低工程复杂度,因为 Value Object 可以作为一个值的副本直接拷贝到新的 AGGREGATE 中
  • Factory(工厂):如果创建一个对象或者 AGGREGATE 很复杂,可以用 Factory 来屏蔽内部细节
    • 被创建的对象不适合承担复杂的装配操作(比如创建一个对象需要大量的参数),把这些职责耦合在一起会产生拙劣的设计,让客户直接通过构造方法创建对象会暴露过多的技术细节并且无法保证用户满足了所有的原子诉求,因此需要用 Factory 封装这些细节
    • Factory 通常会被隐藏在 AGGREGATE 中,用于事务性的创建一个完整的 AGGREGATE
    • 不要一味的套用 Factory:如果被创建的对象足够简单或者该对象的构造方法中没有调用其他对象的构造方法,那么就直接使用该对象的构造函数就行了
    • 两种 Factory
      • Entity Factory: 只要求用户传入核心的字段,非核心的字段可以创建完对象后再添加
      • Value Object Entity:生成的就是最终的对象(比如 String)
  • Repository(仓库)
    • 访问 AGGREGATE 根的时候如果很难通过遍历找到根,那就通过 Repository 来提供查询。不要随意直接绕过领域层使用底层的基础设施提供查询,这会打破领域模型
    • 只为AGGREGATE的根提供 Repository 可以让开发者使用聚焦与模型,所有对象的存储与访问可以交给 Repository
    • Repository 的优点:
      • 为开发者提供了一个简单的模型,并且可以获取持久化的对象并管理他们的生命周期
      • 不与具体的存储技术实现耦合,可以随意替换底层的技术设施实现
      • 很容易替换成“哑实现”,比如从操作数据库改成操作内存,方便测试模型
    • 良好的 Repository 设计应该是替换基础设施时,上层代码不用改动

第7章:一个扩展的示例(通过一个运输的例子说明一些概念的应用)

  • 如果已经做好的领域模型系统要用到另一个系统,可以把另一个系统抽象成 Service 与本系统进行结合,好处是避免利于模型被污染
  • Enterprise Segment(企业部门单元)模式——《分析模式》Fowler 1996

第三部分:通过重构来加深理解

  • 要想成功的开发出实用的模型,需要注意
    • 复杂巧妙的领域模型是可以实现的,也是值得我们花费时间去探讨的
    • 巧妙的模型是通过重构优化出来的,不可能一开始就有非常完美的模型
    • 要实现并有效的运用模型,需要精通设计技巧
  • 重构的层次: 运用成熟的设计模式进行重构,那么重构时就可以在项目中真正的体会到设计模型的好处
  • 深层模型:**深度表现领域的模型通常都有领域专家常提到到的一些抽象概念,这些抽象概念都别放到了 UBIQUITOUS LANGUAGE 中并作为模型的一部分。**恰当的反应领域的模型通常具有功能多样、简单易用跟解释力强的特性,他们都提供了一种领域专家青睐的简单语言(尽管这种语言可能是抽象的)
  • 柔性设计:每次对模型与代码的改进都能体现出对领域的新理解,通过不断的重构就能为系统最需要修改的地方提供灵活性

第8章:突破

image.png

  • 通过微小的重构可以量变引起质变,引起模型的突破并大幅提升开发效率
  • 重构的原则是小步前进、始终保证系统的正常运转
  • 关注领域的根本
    • 不要试图制造突破
    • 不要因为重构的点小就不做重构,每次重构都是对领域新的理解,不要好高骛远想把事情一步做到位

第9章:将隐式概念转变为显式概念

  • 深层建模的第一步是讲一些浅显易懂的概念表达在模型中,随后就需要跟领域专家探讨,将一些之前不起眼的(开发者自己认为)的概念也同样表达出来。一些不起眼的概念往往能解决模型的核心问题。
  • 概念挖掘
    • 倾听语言:看是否有一些领域专家经常提到的术语或概念是否没有在模型中体现出来,往往这些术语会促成改进模型与设计
    • 自查不足:重要的领域概念需要开发者去挖掘(模型中操作复杂且难以解释的地方,每当有新的需求时,这个地方往往会变得更复杂)
    • 思考矛盾之处:由于经验与需求的不同,不同的领域专家对同一概念的理解不同,开发者需要把这些矛盾之处找出来并且仔细分析逻辑上不一致的地方用于更改深层模型。不用幻想着解决所有矛盾(不同的领域专家对相同的概念有不同的解释,但是他们都用这个概念作用于自己的领域,我们只需要找到为什么大家观点不同但是可以使用同样的概念描述同一事物就足以优化深层模型了)
    • 查阅书籍:查领域相关的数据或者前辈开发者对相同领域的文献资料
    • 不停的尝试:建模人员/设计人员绝对不能固执己见,善于打破自己的固有观念去贴合领域
  • 如何为那些不太明显的概念建模
    • 显示的约束:把一些约束条件直接提取到一个方法中,这样就可以通过方法名来表达约束含义,从而在设计中显示的表达出这条约束。(比如往一个容器中倒水,每次倒进去的量+已有的量 <= 容器 我们需要把校验的逻辑单独抽一个方法出来而不是在 put 的方法中直接做校验)
    • 将过程建模为领域对象:约束与过程(比如:Service)是两大类模型概念,我们使用面向对象的编程范式时不会立即想到他们,但是一旦我们把这些概念明确的表达到模型中,会让我们的设计更清晰
    • SPECIFICATION(规格):一些校验对象是否符合条件的方法可以被建模成“规格”,这些“SPECIFICATION”会被保留在领域层。从而防止模型劣化
    • SPECIFICATION的应用
      • 验证对象,判断该对象是否满足一些条件
      • 从集合中选择一些对象(通常与 Repository 结合起来,查询使用 Repository,Repository 中可以使用不同的SPECIFICATION做查询)
      • 制定创建对象时必须满足某种需求(通常与Factory或构造函数这类的生成器结合起来)

第10章:柔性设计

image.png

  • 软件为用户服务的同时也为维护它的开发者服务,良好的架构设计在软件迭代的过程中可以很容易的被新的维护者接受并重构优化。这就是所谓的柔性设计(手套关节处会被设计的很柔软,其他的地方会很坚硬)
    • 当项目的复杂性阻止了前进的脚步,就必须仔细修改最关键、最复杂的地方使之变成一个柔性设计
  • INTENT-REVEALING INTERFACES(意图显示接口/释意接口):让类、方法见名知意,使用者看到名字就知道这个类或方法是干啥的而不用管内部的实现
    • 类名或方法名应该与 UBIQUITOUS LANGUAGE 保持一致,以便团队的成员可以迅速推断出他们的含义。
    • 加上对应的测试用例可以促使我们站在使用者的角度去思考,并且可以帮助使用者更高效的了解类的使用
  • SIDE-EFFECT-FREE FUNCTION:
    • 程序中的操作可以分为命令与查询,命令会引起系统的变化,命令之间复杂的调用会导致整个工程变得难以维护。把副作用逻辑与能真正引起系统变化的逻辑分离开能让系统更清晰(Redux 中的 effects reducer 就是这种思想)
  • ASSERTION(断言)
    • 把一些条件或者规则写明白放到文档或者图中,然后在代码中直接加入强制性的断言可以让后面的维护者更快的知道规则
    • 在概念上寻找内聚的模型,让开发者更容易推断出预期的断言,可以加快学习过程并避免代码矛盾
  • CONCEPTUAL CONTOUR(概念轮廓)
    • 通过反复重构可以得到一个柔性设计的原因就是领域的模型轮廓逐渐清晰了
    • **找到领域中的变与不变!!!:**通过连续的重构过程观察系统中发生变化跟保持稳定的规律性,并寻找能够解释这些变化模式的底层 CONCEPTUAL CONTOUR。使模型与领域中的那些稳定的方面(正是有这些稳定的方面才构成了这个领域的知识体系)相匹配
    • 如果遇到一个需求,我们需要大幅的修改对象与方法的划分,这就表示我们的模型还需要精化,我们对领域的认知还需要进一步提升,并且这个需求也带给我们让系统更柔性的机会
  • STANDALONE CLASS
    • 低耦合是减少概念过载的最基本的方法。独立的类是低耦合的极致
  • CLOSURE OF OPERATION(闭合操作)
    • 我们对集合中两个任意元素进行组合时,结果仍然在这集合中,这就叫闭合操作。闭合操作可以减轻维护者的心智负担
      • 比如 JS 中 Array.map 返回的还是 Array,虽然这不是一个全闭合操作( map(item) 中的 item 不是 Array ),但是这种半闭合仍然大幅减轻了开发者的心智负担
  • 声明式设计
    • 类似于注解,把代码块变成一种可执行的规格。又比如:两个 Container 类想加可以得到一个新的 Container 类,这个 Container 类的容量是两个子 Container 容量之和
  • 特定领域语言:如果领域过小,可以采用一些非常适合的领域语言来开发比如面向过程的语言
  • 使用模式的时候不要生搬硬套,暂时用不到的东西可以后面再添加,但是要保证模型的完整性
  • 切入问题的角度
    • 分割子领域:重点突击某个部分比泛泛而谈有用的多
    • 尽可能利用已有的形式:珍惜系统中已有的概念、框架并加以利用不要从头开始

第11章:应用分析模式

《分析模式》——Martin Fowler 不同的领域有一些事情是想通的,分析模式就是其他领域中一些经过实践的解决方案,这些解决方案可以为我们解决当前领域的问题提供一些思路

第12章:将设计模式应用于模型

设计模式一般都被用来解决某些技术问题,其实领域模型也可以使用这些模式。比如策略模式一般是将不同的算法封装起来用于无缝替换,但是应用到领域里面就更倾向于突出实现规则或可替换的过程。

第13章:通过重构得到更深层的理解

  • 通过重构得到更深层理解是一个涉及很多方面的过程
    • 以领域为根本
    • 用一种不同的方式来看待事物
    • 始终坚持与领域专家对话
  • 开始重构
    • 即使代码很整洁,只要是软件的模型与领域专家的认知没有保持一致就需要重构
  • 探索团队:挑选团队中善于思考的人
  • 借鉴先前的经验
    • 从书籍或者领域自身的其他知识源获取思路
  • 针对开发人员设计
    • 柔性设计主要通过减少依赖和副作用来降低开发人员的心智负担。在模型中,只有对用户最终要的部分才具有较细的粒度,那些经常需要修改的地方能保持很高的灵活性,其他的地方则相对简单
  • 重构的时机: 应该坚持把“通过重构得到更深层的理解”作为工作的一部分
    • 现有设计没有表达出团队对领域的最新理解
    • 重要的概念被隐藏在设计中并且你找到了显示表达他的方法
    • 发现了某些操作能使一个重要的设计部分变得更灵活
  • 危机就是机遇:通过不断的重构我们就能获得突破

第四部分:战略设计

image.png 战略设计的原则必须把模型的重点放在捕获系统的核心概念上,也就是系统的“远景”上。并且在完成这些目标的同时不能为项目带来麻烦。这部分主要探索“上下文”、“精炼”、“大型结构”

第14章:保持模型的完整性

  • 模型最基本的要求是保持内部一致性,术语具有相同的意义,并且不包含相互矛盾的规则。(比如同样叫 topic 的定义,同一个模型中只能有一种解释。不能在模型中酒店解释成酒店主题、领券解释成活动主题)
  • 不要指望能统一大型软件的模型,大型系统的领域模型完全统一既不可行也不可靠。
  • BOUNDED CONTEXT(上下文边界)
    • 细胞之所以存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么可以通过细胞
    • 任何大型的系统都会同时存在多个模型,当基于不同的模型代码被组合在一起时,整个软件系统就变得不可靠并且沟通成本急剧增加(比如相同的类名在不同的模型中含义不同)
      • 解法:明确的定义模型应用的上下文,根据团队的组织、软件系统各个部分的用法以及物理表现(代码或者其他)来设置模型的边界,在边界内严格保证模型的一致性,并且不受模型外的影响
    • BOUNDED CONTEXT优势:CONTEXT 内的团队变得更加清晰,CONTEXT外的团队更加自由
    • 识别 BOUNDED CONTEXT 中的不一致:
      • 已编码的接口不匹配
      • 重复概念 & 假同源
  • CONTINUOUS INTEGRATION(持续集成)
    • 当很多人在同一个 BOUNDED CONTEXT 中工作时,模型很容易发生分裂,团队越大,问题就越多:CONTINUOUS INTEGRATION 是指把一个上下文中的所有工作足够频繁地合并到一起,当模型发生分裂时可以及时的发现问题
    • 不要在持续集成时做一些不必要的工作,因为这个过程太频繁了。CONTINUOUS INTEGRATION 只有在同一个 Bounded context 内才有意义
  • CONTEXT MAP

image.png

  • Context map 是位于项目管理和软件设计的重叠部分,目的是把不同模型之间的联系点、需要通信的地方、通信需要的转换都明确出来并且突出任何共享的内容
  • Context map 不用拘泥于特定的文档规格,重要的是它必须被项目人员之间共享并被他们理解。 Context map 必须为每个 bounded context 提供一个明确的名称并且必须明确的阐明他们之间的联系及他们的本质。**
  • 两个 bunded context 之间的接口应该足够小并且无副作用(方便测试)
  • 及时测试两个 bunded context 之间的的联系点,尽早发现问题
  • CONTEXT MAP 的组织与文档化
    • MAP 中的 Bounded context 应该有名称,并且这些名称应该被添加到团队的 UBIQUITOUS LANGUAGE 中
    • 每个人都应该知道边界在哪里,并且可以从一些别的角度快速知道这个类属于哪个上下文(比如类命名或者 Bundle 名)
  • BOUNDED CONTEXT 之间的关系
    • SHARED KERNEL(共享内核)
      • image.png
      • 两个团队都同意共享的一个子集,任何一个团队在在没有周知其他人的前提下不允许修改这个子集(比如大家都用了 AjxVersionUtils 这个类,不允许有人私自改动)
    • CUSTOMER/SUPPLIER DEVELOPMENT TEAM(客户/供应商团队)
      • 两个团队之间如果建立一种明确的上下游关系(比如上层业务团队依赖下面的底层架构团队)
      • 核心要点:1. 上下游都归属于同一个管理者 2. 供应商不能对客户的需求有偏见(穷亲戚现象,客户得求着供应商)而不支持 3. 必须有自动化测试套件以便供应商团队修改代码时不需要提心吊胆怕影响下游
    • Conformist(跟随者)
      • 如果你强依赖上游团队,但是上游团队又不给你提供支持,那么那可以将你的模型**“跟随”**上游团队模型,以便降低转化成本
    • ANTICORRUPTION LAYER(转换层)
      • image.png
      • 原有系统可以通过一个转换层为新的模型提供服务,并且不改变新的模型
      • 常用的实现转换层的设计模式**(Facade、Adapter)**
    • SEPRATE WAY(各行其道)
      • 集成总是代价高昂的,收益有时会很小。所以各行其道也是一种好的解决方案
      • 声明一个与其他上下文毫不关联的 Bounded Context,使开发人员能够在这个小范围内快速找到解决方案
    • OPEN HOST SERVICE(开放主机)
      • 当子系统有多个系统集成时,就需要使用开放主机服务。我们要定义一个协议,把你的子系统作为一组 Service 供其他系统访问。开放这个协议以便所有需要与你子系统集成的人都可以使用它
    • PUBLISHED LANGUAGE
      • 比如都使用 SQL 描述数据库协议,SQL已经足够强大了完全可以 Cover 两个业务模型之间的交互需求(又比如 XML 这种协议)
  • 通过集成统一模型
    • 集成的第一步只需要弄清楚各个部分是如何相连的就足够了(绘制出清晰的 Context Map)
    • 第二部是去掉各个模型中那些偶然或不正确的方面,并创建新的概念(每个人都得承认自己的模型都有不合理的地方)
  • 模型合并的步骤
    • SEPARATE WAY -> SHARED KERNEL
    • SHARED KERNEL -> CONTINUOUS INTEGRATION
    • 逐步淘汰遗留的系统,这个过程会很长并且不要幻想着一口吃个胖子
    • OPEN HOST SERVICE -> PUBLISHED LANGUAGE(如果过多系统集成了你的子系统,那你的子系统维护成本会剧增,因此需要使用一种业界成熟的标准语言来集成(比如XML),如果没有成熟的语言,那么就使用 HOST 系统的 Core Domain 来作为 published language)

第15章:精炼

精炼是把一堆混杂在一起的复杂问题分开的过程,让开发者可以关注核心问题而不被大量次要的问题所淹没——模型就是知识的精炼

image.png

  • Core Domain(核心领域):我们必须对模型进行提炼,找到 Core Domain 并提供一种可以把核心领域与其他辅助作用的模型分开的方式(代码注解或者组织轮廓),并且尽量压缩 Core Domain
    • 选择核心:我们需要关注的是那些能够表示业务领域并且解决业务问题的模型部分
      • 一个程序的核心领域可能在另一个程序中只是辅助角色
      • 人们对 Core Domain 的理解也会随着认知的发展而发展
    • 工作的分配:让最核心的人去开发 Core Domain 而不是基础设施(就像很多牛逼的程序员都热衷于搞架构,其实业务问题才是最重要的)
  • Generic Subdomain(通用子领域):非核心的通用的(比如通用时间转换器)子模型
    • **优先解决主要矛盾:**这些通用的子领域当然也是模型,但是不是核心的。如果花费大量的时间到这上面就会出现核心模型被耽搁的情况
    • 识别出与项目意图无关的子领域,并且把这些子领域的模型提取出来,放到单独的 Module 中维护。后续的维护过程他们的优先级要低于核心子领域的优先级,人员配备上也要把核心的开发人员放到核心子领域中
    • 通常通用子领域已经有很多现成的解决方案了,为了节省成本可以直接采用现成的方案
      • 购买现成的解决方案或者源码
      • 已经公开的通用模型或设计
      • 外包给别人实现
      • 内部实现
  • 通用不等于可重用(不要过度设计,只做当前用到的东西就够了,后面可能会用到的东西把概念设计出来就足矣了)
  • 项目风险管理:任何任务的优先级都不能搞过 Core Domain,且 Core Domain 的难度往往会超过我们的想象
  • Domain Vision Statement(领域愿景说明):用最简单的话术描述最核心的功能或者愿景(内容一定要精简),不用描述具体的实现
    • 通过 Domain Vision Statement 可以让大家清晰的知道团队方向是啥,往同一个方向努力
  • Highlighted Core(突出核心)
    • 精炼文档:编写一个 3-7 页的简短文档(不要太多!!!),用于描述 Core Domain以及 Core元素之间的交互过程。文档必须足够的简化,才能化解文档过时的危机
    • 标明 Core:不管是通过 UML 还是通过 Java Doc 的方式,把 Core Domain 标记出来,时时刻刻让团队的所有人都知道哪里是核心内,哪里是核心外
    • 把精炼文档当做过程工具:修改精炼文档意味着要改模型的核心,这时候要拉着团队的所有人一起商量。精炼文档之外的东西可以使用敏捷过程
  • Cohesive Mechanism(封装机制):把复杂的算法、与模型无关的东西抽到单独的 SDK 中
  • Cohesive Mechanism 与 Highlighted Core 差异点在于 Highlighted Core 仍然是模型,只不过不是核心的模型,而Cohesive Mechanism只是一些核心、复杂的支持性算法(比如算路)而已,不是模型
  • 绕弯路后又回到原来的老路上是非常正常的事情,但是这不意味着回到了原点。这是因为我们获取到了一个更深层的模型。所以不要老以为别人改了架构或者框架是为了 KPI !!!
  • Segregated Core(分离的核心):如何分离核心的领域呢? 简单来说就是对模型就行重构,把核心的元素从众多支持性的元素中分离出来,并且增强 Core 领域的内聚同时减少与其他代码的耦合程度。常用的步骤如下:
    • 识别:识别一个 Core 子领域,通常可以从精炼文档中识别
    • 代码整理:把相关的类移动到新的 Module 中,并且根据这些类有关的概念为模块命名
    • 旧代码重构:把那些不直接表示概念的数据或者功能拆分到单独的包中
    • 新代码重构:对新的 Segregated Core Module 进行重构,增强内聚并且使他们内部的逻辑变得更简单
    • 重复过程 1 ,开始识别新的子领域
  • 创建 Segregated Core 后可能原有的调用关系就得重构了,但是这有助于创建柔性的设计并且有利于 Core Domain 的创建,有助于整体工程的稳定与高可用
  • 任何突破对项目来说都有价值,但是只有 Core Domain 的突破才能改变整个项目的轨道
  • 如何选择重构的目标? 优先选择 Core Domain 的重构

第16章:大型结构

image.png 在一个大型的系统中,如果没有一种全局的原则来明确元素的位置与地位时,一线的开发者就会陷入“只见树木,不见森林”的囧境中。(就像很多一线的同学不知道一个业务代码应该放在什么 Bundle 中,因为缺少一种全局的原则,所以才需要 Bundle 治理)。因此我们需要一种全局的原则来明确系统的各个部分在系统中的作用,避免一线开发者出现盲人摸象的现象

  • Evolving Order(持续演进的结构)
    • 业务架构在设计时可能做了很多假设或者限制,在当时的环境下是没问题的,但是随着时间的变化,这些限制变得越来越不合理并且开始阻塞业务的发展(比如一些限制不让业务代码写 TS 等),**因此业务架构是需要随着业务一起演进的,**有好的架构如果能为业务提高效率那就应该果断的使用它
  • System Metaphor(系统比喻)
    • 可以把正在做的系统比喻成一个家喻户晓的东西,比如可以把物流中的包裹运输类比成 Java 的 GC机制,这样大家就知道自己所做的工作是可达性分析还是标记算法的设计
  • Responsibility Layer(职责层)
    • 代码中的对象职责都是人为赋予的,可以把分层与职责驱动这两大设计原则结合起来,进行概念上的分层。让一线的同学明确的知道自己的职责后就知道自己的代码在实现哪块业务逻辑
  • Knowledge Level(知识级别):“反射”机制在模型领域的应用,避免模型数量爆炸。了解就好,基本用不到
  • Pluggable Component Framework(可插拔组件框架)
    • 多个 Bounded Context 相互交换数据时,如果他们都是基于同一个 Abstract Core 那么他们之间是可以互相替换的。因为接口都是一样的。但是项目的初始阶段不建议使用这种框架,因为项目高速迭代的过程中很难全面的考虑到需要沉淀到 Abstract Core 中的能力到底应该是什么。
  • 一些原则:
    • 不要试图让结构面面俱到,能解决主要问题就好,其他的问题可以留到后面一个一个解决。

第17章:领域驱动设计的综合运用

  • 把大型结构跟 Bounded Context 结合起来
    • image.png
    • 大型结构可以只存在于同一个 Bounded Context 中也可以横跨多个 Bounded Context
    • 如果你想在遗留的系统中使用 Responsibility Layer,那么可以将遗留系统合理的划分到多个层中而不是只在一个层
      • image.png
  • 制定战略设计决策的 6 个要点
    • 决策必须传递到整个团队
    • 决策过程必须搜集反馈意见
    • 计划必须允许演变
    • 不要把所有牛逼的人都集中到架构团队,也需要有部分业务架构师
    • 战略设计要简约
    • 每个开发人员都应该是多面手(架构团队的人也要写业务,业务人员也要参与架构设计)
  • 注意总体规划:总体规划大概率会失败,因为他无法形成一个有机秩序。总体规划过于精细但是又不够细致,这就会导致实施的过程中大家会抛弃他。SO:学会聚少成多的成长(不积跬步无以至千里)

欢迎大家微信扫下面二维码,关注我的公众号【趣code】,一起成长~ center