笔记 - 《领域驱动设计:软件复杂性应对之道》

简介: 笔记 - 《领域驱动设计:软件复杂性应对之道》

最近翻了一下《领域驱动设计-软件复杂性应对之道》这本书,翻完之后,由于很难完全静下心来以及自己的经验尚浅,总感觉体会不是很深,所以为了加深理解并方便后续回味,觉得需要再梳理一下并记录一下,故结合书中的一些知识点和本人的一些思考写下本文。

通过本文,你可以:

1)了解到领域驱动设计的一些名词和概念。

2)看到一些结合这些元素的示意图,便于理解和记忆。

1 道是什么

这本书的子标题还是很吸引人的:软件复杂性应对之道。看完这本书就能得其"道"了吗?它讲的”道“是什么那?本人看完之后刚开始是比较失望的,因为联想了日常的工作,好像并没有实质性明确的方法步骤,能解很好地告诉我该如何解决软件复杂性。

但是真正的道不正是如此吗。我们曾做过无数的试卷,并没有哪本试题集可以把所有的题型介绍完,但是每到题拆解下来,它的知识点都是可以映射到考点里面、书本中的。

我们想直接看到题目的答案,却忽略了锻炼做题的思路。很多人做错后,归结于智商(能力)的问题,但本质还是没有抓住重点。我们需要研究问题的解法和策略,思考解题的思路,并进行类似题型的训练,才能把短期的信息沉淀为长期的知识,把别人的”智商“转化为自己的能力。

image.png


1.1 注重基础概念

作者在提出领域驱动设计的概念之后,花了很多篇幅去介绍一些非常基本的概念。比如:实体 entity、值对象 value object等。很多人根据仅有的经验,会认为就是换一种称呼而已,我们内部有自己的一些命名也是差不多的意思。是的,单个对象的定义本身就是很简单,但是正是这些简单的定义才能演化出复杂的推导。

西方文明是讲逻辑的,通过大前提小前提等前提条件推出对应的结论,但是还是很多人是以经验主义为主的,根据以往的经验能够快速地对号入座,给出理解。但是这样的经验主义,如果遇到超出经验之外的,就很难推导出新的结论。就好比是一个神经网络,看上去训练很方便,但是要解释其运作的原理其实是很困难的,后面需要不断地调参,获取更好地效果的成本也很大。

所以,我们的应对之道非常重要的一点是建立大家公认的简单的基础概念。通过这些可以进一步进行演化推演,获得更高级的知识,从而从容应对复杂问题。

1.2 沉下心来

作者曾在书中不止一次地表达,好的模型需要不断演化。的确,我们实际解决的问题和固定的考试题目是不一样的。我们的要解决的业务场景问题是不断发展变化的,即使现在看起来完美的解决方案,可能过一个阶段就难以胜任了。

就好比射击运动员打移动的靶子一样,如果你无法预估靶的运动位置,那么就很难命中。我们要想很好地解决业务问题,需要看到业务发展的趋势,甚至业务所在整个大背景下的趋势。但是也不能过度设计,这样很可能靶还没有到那个位置,你就急于出手而错过了。

但是对整个业务发展把握得非常准确的,是少之又少的。根据一万小时定律,一个人要成为某个领域的专家,至少需要5年。有多少人能在一个领域坚持下来那?运动员也是需要无数个日夜刻苦地训练,才能成为一个神枪手。

所以,说了这些,深刻地感受到,领域驱动设计不是一个简单地事情,他是需要你不断坚持练习,并不断根据效果调整自己判断的长期训练过程。所以,软件复杂性应对之道还需要我们要有良好的心态,这是一个持续打靶的过程,每个阶段我们能很好地解决一部分问题就已经很庆幸了。

2 基础概念

“为什么努力了还是没有成功?当我们遇到这个问题时候,我们不得不去想一下:我们知道该怎么做好吗?如果只是凭着一腔热血,盲目地重复,那么你再努力,也没有实质性的变化。举个极端的例子,一个小学生不管如何努力,是解不了微积分这样的大学问题的。不是说他不是个好学生,是他根本没有基础知识的储备,没有这些基石,他只能去猜,这注定是无法解决问题的。

所以,领域驱动设计非常重要的一块是基础概念的介绍。接下来需要再体会理解一下。

2.1 构造块

构造块是指领域设计中最基本的一些元素。“不积跬步,无以至千里”,接下来看一下这些基础元素。

image.png

2.1.1 分层架构

分层是常用的方法,不仅仅是领域驱动设计,我们在学习计算机网络的时候也会看到分层的思想:网络的七层模型。分层的好处是显而易见的:可以让我们集中精力关注每一层的功能职责,更好地理解,更好地进行分工协作。

要注意的是,分层的基本原则是:层中的任何元素都仅依赖于本层的其他元素或其下层的元素。下层如果不得已要调用上层的话,需要采用合适的模式,如观察者模式或者回调机制等。

常见分层如下:

1)用户界面层(或表示层):展示信息等。如:web 服务的 Contoller。

2)应用层:协调领域对象。如:支撑 Contoller 的 Service 服务。

3)领域层(或模型层):表达业务概念、业务状态信息、业务规则。

4)基础设施层:为上面各层提供通用的技术能力。如:数据库操作等。

2.1.2 关联

我们的模型其实是真实世界模型的一个子集,我们不需要每个细节都需要反映出来,关联关系也是如此。

关联描述的是对象之间的联系,作者描述了3种减少关系复杂度的方法:

1)规定一个方向遍历。如:学校和校长是多对多的双向关系,但是如果可以考虑一个校长只在一个学校担任的职务的话,可以简化为学校到校长的1对多的关系。

2)添加一个限定符,以便有效的减少多重关联。如:在学校的某个特定时期,只有一位正校长。

3)消除不必要的关联。按需描述即可,less is more。

image.png

2.1.3 entity

实体 entity 的定义:主要由标识定义的对象被称做entity,特点是在整个生命中具有连续性。

简单理解为领域层中带有意义ID的对象,如:订单信息(含订单ID)。

2.1.4 value object

值对象 value object 的定义:用于描述领域的某个方面而本身没有概念标识的对象称为 value object。

简单理解为对象属性决定差异的个体,如:颜色RGB对象,只要rgb值一致,那么其实是一种颜色,并不关心对象的本身到底是哪个实例。

这类对象特点:

1)经常作为参数在对象之间传递消息,常常是临时对象。

2)设计选择:复制、共享或保持value object不变。

3)为性能优化提供了更多选择:可缓存、可共享等。

2.1.5 service

领域层中服务 service 的特征:

1)与领域概念相关的操作不是 entity 或 value object 的一个自然组成部分。

2)接口是根据领域模型的其他元素定义的。

3)操作是无状态的。

简单理解为领域层中一些需要协调多个对象的无状态函数。可以暴露领域中的一些功能。

值得注意的是:

1)service不只是在领域层使用,根据职责不同,service也会分到各层中。

2)可以控制领域层中接口的粒度。

2.1.6 aggregate

聚合 aggregate 是一组相关对象的组合,有一个根(root)和一个边界(boundary)。外部对象只能引用根,而边界之间的内部对象之间则可以相互引用。

image.png

2.1.7 factory

工厂 factory 负责复杂对象和aggreate的创建,是领域设计的一部分。

2.1.8 repository

存储库 repository 为那些需要直接访问的对象提供数据访问能力,封装底层存储逻辑。

2.1.9 specifiction

声明 specifiction 是为特殊目的创建了谓词形式的 VALUE OBJECT,是一个谓词,可以用来确定对象是否满足某些标准。

场景:某些业务规则不适合作为Entity 或 VALUE OBJECT 的职责。

2.2 柔性设计

当我们对领域模型有了深沉次的理解后,就能设计出更”软“的软件,下面介绍一下作者介绍的一些柔性模式。

柔性设计(supple design):运用深层模型所蕴含的潜力来开发出清晰、灵活且健壮的实现,并得到预期的效果。

image.png

2.2.1 intention-revealing interfaces

释意接口 intention-revealing interfaces:在命名类和操作时要描述它们的效果和目的。

2.2.2 side-effect-free function

无副作用函数 side-effect-free function:尽可能把程序的逻辑放到函数中,函数不产生明显的副作用,把命令(引起明显状态改变的方法)隔离出来。

2.2.3 assertion

断言 assertion:对程序某个时刻正确状态的声明,在编程中编写 assertion 或者单元测试。

2.2.4 conceptual contour

概念轮廓 conceptual contour:领域本身的一致性,把设计元素分解为内聚的单元。

2.2.5 standalone class

孤立的类 standalone class:无需引用任何其他对象(系统的基本类和基础库除外)就能够理解和测试的类。把无关对象提取到对象之外,保持低耦合。

2.2.6 closure of operation

闭合操作 closure of operation:闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖。在适当的情况下,可以定义操作的返回类型与参数类型相同。

2.3 战略设计

当我们的系统不断复杂起来,或需要和其它团队系统进行交互的时候,我们需要看到更高层次的策略。下面的描述的策略关注了系统之间交互的关系。

战略设计(strategic design):一种针对系统整体的建设和设计决策。

image.png

2.3.1 bounded context

限界上下文 bounded context:特定模型的界限应用。限界上下文使团队知道什么必须保持一致,什么必须独立开发。

2.3.2 continuous integration

持续集成 continuous integration:把工作足够频繁地合并到一起,并使它们保持一致。

2.3.3 context map

上下文图 context map :项目所涉及的界限上下文以及它们与模型之间的关系的一种表示。描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容

image.png

2.3.4 shared kernel

共享内核 shared kernel :通常是共享核心领域或者是一组通用子领域。

image.png

2.3.5 customer/supplier

客户/供应商关系 customer/supplier:上下游关系。不同客户需要协商来平衡,上游团队需要有自动测试套件。

image.png

2.3.6 conformist

跟随者模式 conformist:单方面跟随模式。上游的设计质量较好,容易兼容,可以采用严格遵循上游团队的模型。

image.png

2.3.7 anticorruption layer

防腐层 anticorruption layer:防腐层、隔离层,使用 facade or adapter 等模式。可以减少其它系统变动对本系统的影响。

image.png

2.3.8 separate way

各行其道 separate way:声明一个与其它上下文毫无关联的 bounded context,使开发人员能够在这个小范围内找到简单、专用的解决方案。

image.png

2.3.9 open host service

开放主机服务 open host service:开放子系统供其他系统访问。

image.png

2.3.10 published language

共享语言 published language:把一个良好文档化、能够表达领域信息的共享语言作为公共的通信媒介,必要时在其它信息与该语言之间进行转换。

2.4 精炼

人们常常感叹数学的优美:它可以通过非常简单的公式来推导并解释复杂的现象。这正是因为它总结得很精准,抓到了问题的本质。

我们在领域建模的时候也不可能面面俱到,这样就太过细节了,注定会陷入其中,缺少前进的动力。我们也需要抓住模型关键的部分。下面就介绍一下作者书中介绍的一些模式。

精炼(distillation):是把一堆混杂在一起的组件分开的过程,从中提取出最重要的内容,使得它更有价值。

image.png

2.4.1 core domain

核心领域 core domain:模型中最关键的部分,是程序的标志性部分,也是应用程序的核心诉求。

2.4.2 generic subdomain

通用子领域 generic subdomain:识别出对项目意图无关的内聚子领域,提取通用模型,并分离出来。

2.4.3 domain vision statement

领域愿景说明 domain vision statement:类似愿景说明文档,关注领域模型的本质,以及如何为企业带来价值。

2.4.4 highlight core

突出核心 highlight core:编写简洁文档描述 core domain 以及核心元素之间的主要交互过程,并把 core domain 标记出来。

2.4.5 cohesive mechanism

内聚机制 cohesive mechanism:把内聚的部分分离到一个单独的轻量级框架汇中, 并用释意接口暴露框架功能,从而使得领域元素关注做什么怎么做转移给了框架。

2.4.6 segregated core

分离的核心 segregated core:把核心概念从支持性元素中分离出来,并增强核心的内聚性。把通用元素、支持性元素提取到其它对象中。

2.4.7 abstract core

抽象核心 abstract core:识别模型中最基本的概念(能表达出主要组件的大部分交互),并分离到抽象模型中(类、抽象类或接口)。抽象模型放在自己的模块中,实现类留在子领域定义的模块中。可以减少模块间依赖的复杂性。

image.png

2.5 大型结构

欲穷千里目,更上一层楼,当我们面对复杂的大型系统时,我们需要站在更高层次去思考解决方案,下面介绍一下作者对大型结构的理解。

大型结构(large-scale structure):一组高层的概念、规则,它为整个系统建立了一种设计模式,能够从更大的角度来讨论和理解系统。

image.png

2.5.1 evolving order

演变有序 evolving order:让概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。

image.png

2.5.2 system metaphor

系统隐喻 system metaphor:当系统的一个具体类比正好符合团队成员对系统的想象,并且能够引导他们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构。例如:用真实大楼的防火墙来类比网络的防火墙。

image.png

2.5.3 responsibility layer

职责分层 responsibility layer:在具有自然层次的模型中,可以围绕主要职责进行概念上的分层,这样可以结合使用”分层“和”职责驱动的设计“这两个有力的原则。

image.png

2.5.4 knowledge level

知识级别 knowledge level:是一组描述了另一组对象应该有哪些行为的对象。当我们需要让用户对模型的一部分有所控制,而模型又必须满足更大的一组规则情况时,可以利用这个模式来处理。可以通过类型的知识,去选择不同的策略。

image.png

2.5.5 pluggable component framework

可插入式组件框架 pluggable component framework:从接口和交互中提炼出一个抽象核心 abstract core,并创建一个框架,这个框架要允许这些接口的各种不同实现被自由替换。


3 后续

本文对书中重要的概念进行了一下摘录并加入简单的理解,同时参考作者的思维导航图绘制了几个大图建立思维索引,方便后续实践时检索。

虽然有了这些基础概念,但其实本人对如何进行领域驱动设计还是比较模糊的,发现很难结合到日常的工作中。这个一方面可能是我们的场景还没有那么复杂,毕竟模型是对真实的世界建模;另一方面,在阅读的过程中也发现了一些高频名词“贫血模型”、“富血模型”。

它们的含义是:

1)贫血模型:领域对象里只有get和set方法(POJO),所有的业务逻辑都不包含在内而是放在应用层。

2)富血模型大多业务逻辑和持久化放在领域对象里面,应用层只是简单封装部分业务逻辑以及控制事务、权限等。

            image.png

作为业务部门,一般我们日常的开发,基本上以贫血模型为主,快速迭代上线,领域对象基本都退化成了数据对象。当然不是说一定要用“富血模型”,毕竟合理的领域划分还是很有挑战的,没有深刻的理解反而弄巧成拙,徒增复杂度。

但是我们还是需要大量的实践感悟。一方面可以去关注领域设计的机会,另一方面也可以阅读别人的一些实践文章。

相关文章
|
7月前
|
测试技术 uml
【软件工程】揭秘需求工程的奥秘:构建成功软件的基石
【软件工程】揭秘需求工程的奥秘:构建成功软件的基石
|
设计模式 消息中间件 架构师
如何成为更好的软件架构师?
如何成为更好的软件架构师?
|
消息中间件 缓存 数据可视化
领域驱动设计对软件复杂度的应对
领域驱动设计对软件复杂度的应对
领域驱动设计对软件复杂度的应对
|
数据可视化 架构师 前端开发
复杂性应对之道 - 领域建模
复杂性应对之道 - 领域建模
复杂性应对之道 - 领域建模
|
Java
复杂性应对之道——抽象
写本文的原因是,抽象是软件设计中最重要的概念。但抽象这个概念本身又很抽象,我们有必要花一些时间深入理解抽象、抽象的层次性,以及不遗余力的不断提升我们抽象能力。
2852 0
|
关系型数据库
企业应用架构实践(复杂性应对之道)
#前言 从业这么多年,接触过银行的应用,Apple的应用,eBay的应用和现在阿里的应用,虽然分属于不同的公司,使用了不同的架构,但有一个共同点就是都很复杂。导致复杂性的原因有很多,如果从架构的层面看,主要有两点,一个是架构设计过于复杂,层次太多能把人绕晕。另一个是根本就没架构,ServiceImpl作为上帝类包揽一切,一杆捅到DAO(就简单场景而言,这种[Transaction Script]
10120 2
|
架构师 测试技术 数据库连接
|
程序员 领域建模
《领域驱动设计:软件核心复杂性应对之道(修订版)》—第1章 1.2节知识消化
金融分析师要消化理解的内容是数字。他们筛选大量的详细数字,对其进行组合和重组以便寻求潜在的意义,查找可以产生重要影响的简单表示方式——一种可用作金融决策基础的理解。
1444 0
|
程序员
《领域驱动设计:软件核心复杂性应对之道(修订版)》—第1章 1.4节知识丰富的设计
通过像PCB示例这样的模型获得的知识远远不只是“发现名词”。业务活动和规则如同所涉及的实体一样,都是领域的核心,任何领域都有各种类别的概念。
1624 0