前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、什么是复杂系统

复杂系统是由大量相互作用的部分组成的系统。与整个系统比起来,这些组成部分相对简单,没有中央控制,组成部分之间也没有全局性的通信,并且组成部分的相互作用导致了复杂行为

在软件系统中,函数、类、模块、组件和服务等都可以视为组成部分,他们之间的相互作用最终导致了软件系统的复杂行为

1、复杂度成因

image-20230110160410751

理解能力\预测能力 有序 复杂难测 混沌
复杂难测 手表:手表结构复杂,功能明确可预测 城市:城市建设的空间结构、人员结构都比较复杂,需要人花较多时间才能熟悉,城市的规划也随时间存在不确定性风险,比较难以预测结果 股市:影响因素多且复杂,不可控,不可预测,是个典型的混沌模型
简单 内衣:原理简单,功能单一 三人团队:需要通过简单的沟通和协作,做到团队成员间的角色和职责清晰可控 双摆:结构简单,但是对初始设置具有高度敏感性,其行为不可预测

image-20230110160507690

2、理解能力

开发人员对软件系统的理解能力主要在规模、结构

1)规模

a、需求的数量

软件系统的需求决定了系统的规模,一般情况下,数十万行的代码行肯定不能与数千万行的系统规模相比较,而软件系统的规模取决于需求的数量。再者说,需求会生长的,会从一颗小数随着时间慢慢变成参天大树。到了某一个时间点,需求的数量才会慢慢稳定下来。但是当需求线性增长的时候,为了实现这些功能,软件规模也会线性的增长

b、需求的联系

系统规模的扩张,不仅仅是需求的数量,和需求直接的联系也分不开。各种需求功能点直接回相互依赖,修改一处而动全身

c、无法避免的技术债

随着软件系统规模的扩张,软件复杂度也会增长,但是这种复杂度并不仅仅是线性的,而是更加陡峭的指数级别增长,不论是多么厉害的大牛,再今天看似合理的技术方案决策,都会再未来随着系统规模和软件的复杂度变得不堪一击

主动消除和降低技术债:只不过,区别在于债务的多少问题,以及偿还的利息有多高,一般对付技术债的唯一方案就是让技术债变得可见,指定计划主动消除和降低

**技术债的积累: **

需求复杂度增加:多个功能点的开发实现和功能点之间的千丝万缕联系带来了软件规模的的成倍增加。不同业务场景增加了不同的分支。导致圈复杂度的增加。

设计缺陷:如果在设计上没有做到功能之间的正交,就会导致代码维护成本的增加;

bug 引入:没有为业务逻辑编写单元测试,建议功能代码的测试网,就可能因为某一处功能点的修改引入潜在的风险,导致系统运行的风险增加。

技术债的严重性:

纷至沓来的技术债逐渐积累,一旦到了某个临界点,迅速会衰亡,成为遗留系统,这个相信不少开发人员都遇到过。

d、代码行数

软件规模的一个显著的特征就是代码行数,但是,代码行数有欺骗性,有时候代码庞大非常肯能是肥胖病,意味着可能出现了大量重复的代码。代码的圈复杂度或多或少也会影响整个软件系统的规模

2)结构

我们熟悉的迷宫就是一个结构非常复杂的空间,很多规模较小但是却非常复杂的系统就是这样的一个迷宫,这个时候结构成为了决定系统复杂度的一个关键因素,但是归根结底,结构之所以复杂,大多数情况下是由系统的质量属性决定的 比如:我们需要满足高性能的要求,就可能考虑引入缓存,并行处理,线程池,异步消息等等。

a、微服务拆分

纵观软件设计的发展史,一定是不断拆分的微型化过程,拆分后的软件单元不可能单兵作战,怎么通信,怎么协同,数据不一致等问题接踵而来,就是系统拆分后面临的主要问题,这些问题的固有复杂度在特定情况下有可能超过分解带来的收益。

b、分层架构

分层架构的引入是为了维护系统的有序性,但是如果团队不注意维护逻辑分层确定的边界,不按照架构规定的层次分配各个类,各个模块,各个系统的职责。随着团队不注意维护逻辑分层确定的好的边界,就会让边界变的越来越模糊。

最终随着需求的增加,分层架构就失去了原本规划好清晰结构的价值,系统也会变得越来越混乱,最终陷入无序的设计,随着带来的软件复杂度也会大大提高

3、预测能力

影响预测能力的关于是变化,对变化应对不合理,会导致设计不足或者过度设计。我们无法预知未来,肯定就无法预测未来的变化,这就带来软件系统的不可预测性。

在面对可能得变化的时候,我们需要尽可能的保证方案的平衡,既要避免设计不足使得变化对系统产生根本影响,又要防止为了扩展让方案变化的各位复杂。 这非常考验一个开发人员的技术功底

1)过度设计

设计软件的时候,变化总会让我们患得患失,不知道如何把握系统设计的度

⬤ 如果拒绝变化,系统的设计会变得僵化,一旦有新的变化,修改维护的成本将会非常大。

⬤ 如果过于看重变化,希望覆盖一切变化可能,如果预期的变化没有发生,我们之前为变化付出的成本将用于不在补偿回来。

2)设计不足

很多时候,由于设计人员技能不足,没有明确识别出未来确认会发生的变化,或者对需求的变化放心缺乏前瞻,会导致整个设计变得过于僵化,未来修改的成本太高。这样就会走向另一个极端,这就是设计不足

设计不足的方案只顾眼前,对于一定要发生的变化视而不见,这不仅导致方案缺乏可扩展性,甚至有可能出现技术实现方向的错误,这样的实际不是合理的简单设计,而是对于糟糕的质量视而不见,是为了应付进度蒙混过关的临时花招。表面满足了进度要求,但是未来偿还欠下的债务时,需要付出几倍成本。如果整个系统都是都是这样潦草的设计,那么未来说不定某一个需求就会成为压垮这个系统的最后一个稻草

二、领域驱动基本概念

1、介绍

应对软件复杂度的调整,大概是构建软件过程中唯一亘古不变的主题,而能够应对软件复杂度的只能是设计方法,原因是我们无法控制客观存在的问题空间,但是我们可以改变设计的质量,让好的设计为控制复杂度创作了更得机会, 那么如果说 要想克服(业务系统)软件的复杂度,就需要非常严格的使用领域逻辑设计方法,一种有效的领域逻辑方法就是 领域驱动设计

问题1:领域驱动设计是怎么样应对软件复杂度的呢?

答案:领域驱动设计是一种思维方式,影响复杂度的3个因素:规模、结构、变化。控制复杂度的着力点就在这 3 个方面,领域驱动设计对软件复杂度的应对,是引入了一套提炼为模式的 数据元模型对业务软件系统做到了对规模的控制,结构的清晰,以及对变化的响应

2、问题空间和解空间

软件世界可以一分为二,分为构建世界的真实空间和获取解决方案的理想世界。再软件构建过程中,就是从真实世界映射到理想世界的过程。 软件系统的构建其实就是对问题空间的求解,获得构成解空间的设计方案

image-20230721165340621

2、战略设计和战术设计

1)战略设计阶段

战略设计阶段需要从以下2个方面考量

问题空间:对问题空间进行合理分解,识别出核心子领域,通用子领域和支持子领域。并确定出各个子领域的目标、边界和建模战略

解空间:对问题空间进行解决方案的架构映射,通过划分界限上下文, 为统一语言提供语境,并在其边界维护领域模型的统一,每个界限上下文的内部都有自己的架构

2)战术设计阶段

战术设计阶段需要在界限上下文内部开展领域建模,前提是你为界限上下文选择了领域模型,在界限上下文内部,需要通过分层架构将领域独立出来。

image-20230721165447786

2、控制软件的复杂度

1)控制规模

问题空间的规模客观存在,除了在软件构建过程中通过降低客户的期望,要在问题空间控制规模,我们手上的筹码不多,但是如果到了解空间,开发人员就能掌握主动权了。这个时候虽然不能控制系统的规模,但是我们却可以分而治之。将一个庞大的系统持续分分解为小的元素。当然这种分解不是无原则的拆分,在拆分的同时要保证被分解的部分能够合并成一个整体。

问题1:分而治之是一个好方法,但是怎么个分法呢?

答案:领取驱动设计提出了两个重要的设计元模型,界限上下文和和上下文映射。他们是控制系统规模最有效的手段。

a、样例:如果通过界限上下文控制系统的规模

国际报税系统是为跨国公司的驻外出差雇员(系统中被称之为 Assignee)提供方便一体化的税收信息填报平台。客户是一家会计师事务所,该事务所的专员(Admin)通过该平台可以收集雇员提交的报税信息,然后对这些信息进行税务评审。如果 Admin 评审出信息有问题,则返回给 Assignee 重新修改和填报。一旦信息确认无误,则进行税收分析和计算,并获得最终的税务报告提交给当地政府以及雇员本人。

系统涉及功能:

  • 驻外出差雇员的薪酬与福利
  • 税收计划与合规评审
  • 对税收评审的分配管理
  • 税收策略设计与评审
  • 对驻外出差雇员的税收合规评审
  • 全球的 Visa 服务

角色:

  • Assignee:驻外出差雇员
  • Admin:税务专员
  • Client:出差雇员的雇主

采用领域驱动设计后

采用领域驱动设计,我们将架构主要关注点放在了领域,通过分析客户需求以及现在的问题问题空间,在解空间利用界限上下文对系统进行分解后,获得的界限上下文如下

每个限界上下文都是一个独立的自治单元。根据限界上下文的边界划分团队,建立单独的代码库。团队只为所属限界上下文负责:除了需要了解限界上下文之间的协作接口,以确定上下文映射的模式,团队只需要了解边界内的领域知识,为其建立各自的领域模型。系统复杂度通过限界上下文的分解得到了明显的控制。

image-20230721165541435

2)清晰结构

保持系统结构的清晰是控制结构复杂度的不二法门,关键在于,要以正确的方式认清系统内部的边界,界限下上下文从业务能力角度形成了清晰的边界。它与业务模块不同,在内部也有独立的架构。通过分层架构将领域分离出来,在业务逻辑与技术实现之间划定一条清晰的边界

a、业务复杂度和技术复杂度

业务需求带来的复杂度称为 业务复杂度,质量需求带来的复杂度称为 技术复杂度

业务复杂度技术复杂度 并非完全独立,二者相互作用会让系统的复杂度变得不可以预测。要避免业务逻辑的复杂度和技术实现的复杂度混杂在一起,就需要确定业务逻辑与技术实现的边界,从而隔离各自的复杂度,

领取驱动设计 引入的 分层架构 规定了严格的分层定义,将业务逻辑封装在 领域层,将支撑业务逻辑的技术实现封装在基础设施层

3)响应变化

image-20230721172606474

4)冷静认识

控制软件复杂度是构建软件过程中永恒的旋律,必须明确:软件复杂度可以控制,但不可消除

三、领域驱动设计统一过程

领域驱动设计的核心是模型驱动设计,而模型驱动设计的核心又是领域模型,领域模型必须在统一语言的指导下获得。领域模型又可进一步细分为核心子领域通用子领域支撑子域

image-20230725175008295

系统上下文、限界上下文、分层架构 和 聚合都属于领域驱动设计的边界控制手段,他们的区别在于对业务划分的粒度和维度不同。

领域驱动设计的核心诉求是让业务架构和应用架构形成绑定关系,同时降低与技术架构的耦合,使得在面对需求变化时,应用架构能够适应业务架构的调整,并隔离业务复杂度与技术复杂度,满足架构的演进性。

image-20230725175045503

1、全局分析阶段

对现实世界中问题的分析

目标:通过可视化的手段完成对问题空间的探索与分析

任务:通过执行价值需求分析和业务需求分析活动,深入剖析问题空间

活动:价值需求分析、业务需求分析

产物:全局分析规格说明书

1)价值需求分析

利益相关者、系统愿景和系统范围共同组成了目标系统的价值需求,分属于 5W 模型中的 WhoWhyWhere

5W 说明
Who 首先需要识别出目标系统的利益相关者。然后通过统一利益相关者的 业务目标,明确目标系统的界限和方向
Why 最终做到明确系统愿景
Where 识别系统范围

image-20230725175724381

a、利益相关者

支持者,比如组织、部门、员工和上游第三方合作伙伴

受益者,比如用户、下游第三方

注:上游指的是提供价值方,下游指消费价值方。

image-20230725175911349

2)业务需求分析

业务需求分析阶段可分三个层级对业务需求进行逐级的问题拆解,如下。完成问题拆解后,即可梳理出核心子领域、通用子领域和支撑子领域。

第一层:业务流程 when

第二层:业务场景

第三层:业务服务 what

image-20230725180221832

a、业务流程

第一层级,在业务目标指导下梳理出提供业务价值的动态业务流程。属于 5W 模型中的 When

业务流程的起点往往由一个角色向目标系统发起服务请求,而要完成整个流程,则需要多个角色共同参与协作。业务流程的特点是:

1)具有时间属性

2)多角色参与

3)输出业务价值

识别业务流程的两个关键点是:完整和边界。完整是指要具有端到端的完整协作过程,体现一个完整的业务价值;边界仍然是从业务价值层面确定业务的范畴。

image-20230725180412656

b、业务场景

第二层级,按照时间对业务流程进行切分,划分出每个时间阶段的业务场景,每个业务场景时可以由多个角色参与的

场景就是角色之间为了实现共同的业务目标进行互动的时空背景,通过角色在特定时间、空间内执行的活动来推动情景的发展,形成角色与目标系统之间的体验与互动

问题1:业务流程与业务场景的区别与联系

答案:一个动态的业务流程是由一到多个静态的业务场景构成的,业务流程是端到端的完整协作过程,业务场景则是在业务目标的指导下在时间维度对业务流程的纵向切分。

image-20230725180846246

c、业务服务

第三个层级,每个角色在业务场景下的一次功能性交互形成业务服务。业务服务是全局分析阶段的基本业务单元。属于 5W 模型中的 What

1)提供了目标系统的核心价值,满足了利益相关者的价值需求的业务服务,归入核心子领域

2)属于业务需求一部分,但是横向支撑了多个领域服务,不具有明显的个性特征的业务服务,则归入通用子领域

3)起支撑和辅助价值的业务服务,归入支撑子领域

2、架构映射阶段

解决现实世界与软件解决方案的桥接问题,根据全局分析阶段获得产出物(价值需求和业务需求),分别从组织级,业务级,系统级3个层次完成对问题空间的求解。映射为架构层面的解决方案

1)组织级映射

目标是确定系统上下文。根据价值需求分析的结果,通过系统上下文呈现利益相关者、目标系统与伴生系统之间的关系。

2)业务级映射

目标是确定限界上下文根据业务需求分析的结果,对相关的业务服务进行归纳和分类,形成限界上下文。限界上下文内部采用菱形对称架构模式组织业务数据 &服务,限界上下文之间采用限界上下文映射模式解决它们之间的协作问题。

设计限界上下文的四要素:最小完备、自我履行、稳定空间、独立进化

3)限界上下文映射模式

领域模型需要通过限界上线文来限定业务的自治边界,限界上下文之间也需要通过各种方式进行协助,那么根据不同的协作方式,上下文映射关系可以划分为以下 8 中模式:

  • 客户方/供应方
  • 共享内核
  • 遵奉者
  • 分离方式
  • 开放主机服务
  • 发布语言
  • 防腐层
  • 大泥球

4)系统级映射

目标是建立分层架构。分层架构位于系统上下文内部,可考虑如下分层

image-20230725182352389

3、领域建模阶段

对软件解决方案内部进行进一步的分析建模。目标是在限界上下文内部建立领域模型。

领域建模可分为领域分析建模领域设计建模领域实现建模

1)领域分析建模

产出:业务服务规约和领域模型概念图

在统一语言的指导下,通过快速建模法对业务服务进行提炼和抽象,获得领域分析模型。快速建模法是对“名次动词法”的丰富和补充,建立了标准化的领域分析建模过程,它分为如下四个步骤:

image-20230725183124828

名词 说明
名词建模 识别业务服务规约中的名词。名词代表了候选对象,可将其映射为领域模型对象。
动词建模 识别业务服务规约中的动词。动词代表了候选对象上的候选操作,可将其映射为领域行为,若领域行为产生了过程数据(比如单据或交易记录等),则将该过程数据识别为领域概念。
归纳抽象 针对有定语修饰的领域概念进行归纳和抽象。
确定关系 确定领域概念之间的关系。

2)领域设计建模

对完整业务服务展开设计工作,获得领域设计模型

产出:以聚合为核心的静态设计类图和由角色构造型组成的动态序列图与序列图脚本。

方法:角色构造型、服务驱动设计

image-20230725183349789

名词 说明
实体 一个典型的实体应该具备身份标识、属性和领域行为这 3 个基本要素,符合信息专家模式,避免贫血模型。
值对象 不需要被追踪且是不可变的,通常作为实体的属性。
聚合 聚合是介于限界上下文和类之间的一个封装层次,它是边界而不是对象,边界内保证对象的一致性和完整性,对外作为一个整体参与业务行为的协作。
领域服务 如果领域行为是无状态的,或者需要多个聚合的协作,又或者需要访问外部资源,则应该将它分配给领域服务。
领域事件 主要用于表达领域对象状态的迁移,也可以通过事件来实现聚合乃至限界上线文之间的状态通知。
模式 统一语言、模型驱动设计、实体、值对象等 DDD 基本设计元素
资源库 资源库是对数据访问的一种业务抽象,它分离了聚合的领域行为和持久化行为,保证了领域模型对象的业务纯粹性。
工厂 管理聚合的创建过程。

3)领域实现建模

基于 ` TDD` 完成代码设计和单测编写,获得领域实现模型。

产出:实现业务功能的产品代码和验证业务功能的测试代码。

ContactAuthor