0.1. 软件架构模式演进 0.2. 领域驱动设计 0.2.1. 战略设计 0.2.2. 战术设计 0.3. 总结 0.1. 软件架构模式演进 第一阶段是单机架构:采用面向过程的设计方法,系统包括客户端 UI 层和数据库两层,采用 C/S 架构模式,整个系统围绕数据库驱动设计和开发,并且总是从设计数据库和字段开始。 第二阶段是集中式架构:采用面向对象的设计方法,系统包括业务接入层、业务逻辑层和数据库层,采用经典的三层架构,也有部分应用采用传统的 SOA 架构。这种架构容易使系统变得臃肿,可扩展性和弹性伸缩性差。 第三阶段是分布式微服务架构:随着微服务架构理念的提出,集中式架构正向分布式微服务架构演进。微服务架构可以很好地实现应用之间的解耦,解决单体应用扩展性和弹性伸缩能力不足的问题。 在单机和集中式架构这两种模式下,软件无法快速响应需求和业务的迅速变化,最终错失发展良机。 那进入微服务架构时代以后,微服务确实也解决了原来采用集中式架构的单体应用的很多问题,比如扩展性、弹性伸缩能力、小规模团队的敏捷开发等等。 但在看到这些好处的同时,微服务实践过程中也产生了不少的争论和疑惑:微服务的粒度应该多大呀?微服务到底应该如何拆分和设计呢?微服务的边界应该在哪里? 我认为微服务拆分困境产生的根本原因就是不知道业务或者微服务的边界到底在什么地方。 DDD 核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。利用 DDD 设计方法来建立领域模型,划分领域边界,再根据这些领域边界从业务视角来划分微服务边界。 而按照 DDD 方法设计出的微服务的业务和应用边界都非常合理,可以很好地实现微服务内部和外部的“高内聚、低耦合”。 0.2. 领域驱动设计 DDD 是一种处理高度复杂领域的设计思想,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。DDD 不是架构,而是一种架构设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现架构演进。 DDD 包括战略设计和战术设计两部分。 战略设计:从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。 战术设计:从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。 0.2.1. 战略设计 DDD 战略设计会建立领域模型,领域模型可以用于指导微服务的设计和拆分。 事件风暴是建立领域模型的主要方法,它是一个从发散到收敛的过程。它通常采用用例分析、场景分析和用户旅程分析,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系,这是一个发散的过程。 事件风暴过程会产生很多的实体、命令、事件等领域对象,我们将这些领域对象从不同的维度进行聚类,形成如聚合、限界上下文等边界,建立领域模型,这就是一个收敛的过程。 用三步来划定领域模型和微服务的边界。 第一步:在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。 第二步:根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。 第三步:根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。 如上图,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界,所以用虚线表示。限界上下文之间的边界是第二层边界,它可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。 0.2.2. 战术设计 在从业务模型向微服务落地的过程中,也就是从战略设计向战术设计的实施过程中,将领域模型中的领域对象与代码模型中的代码对象建立映射关系,将业务架构和系统架构进行绑定。当我们去响应业务变化调整业务架构和领域模型时,系统架构也会同时发生调整,并同步建立新的映射关系。 DDD 战术设计对设计和开发人员的要求相对较高,实现起来相对复杂。不同企业的研发管理能力和个人开发水平可能会存在差异。尤其对于传统企业而言,在战术设计落地的过程中,可能会存在一定挑战和困难,一定要谨慎评估自己的能力,选择最合适的方法落地 DDD。 0.3. 总结 DDD 是一种架构设计方法,微服务是一种架构风格,两者从本质上都是为了追求高响应力,而从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务出发,其核心要义是强调根据业务发展,合理划分领域边界,持续调整现有架构,优化现有代码,以保持架构和代码的生命力,也就是演进式架构。 DDD 主要关注:从业务领域视角划分领域边界,构建通用语言进行高效沟通,通过业务抽象,建立领域模型,维持业务和代码的逻辑一致性。 微服务主要关注:运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署。
0.1. 领域和子域 0.1.1. 例子-保险业 0.2. 核心域、通用域和支撑域 0.3. 限界上下文 0.3.1. 通用语言 0.3.2. 限界上下文(context) 0.3.3. 限界上下文与微服务的关系 0.4. 实体和值对象 0.4.1. 实体 0.4.1.1. 实体的业务形态 0.4.1.2. 实体的代码形态 0.4.1.3. 实体的运行形态 0.4.1.4. 实体的数据库形态 0.4.2. 值对象 0.4.2.1. 举例 0.4.2.2. 值对象的业务形态 0.4.2.3. 值对象的代码形态 0.4.2.4. 值对象的运行形态 0.4.2.5. 值对象的数据库形态 0.4.2.6. 值对象的优势与局限 0.4.3. 实体与值对象的关系 0.5. 聚合和聚合根 0.5.1. 聚合 0.5.2. 聚合根 0.5.3. 设计聚合 0.5.3.1. 聚合的设计原则 0.5.3.2. 聚合特点 0.5.3.3. 实体的特点 0.5.3.4. 值对象的特点 0.6. 领域事件 0.6.1. 识别领域事件 0.6.1.1. 微服务内的领域事件 0.6.1.2. 微服务之间的领域事件 0.6.2. 领域事件总体架构 0.6.2.1. 事件构建和发布 0.6.2.2. 事件数据持久化 0.6.2.3. 事件总线 0.6.2.4. 消息中间件 0.6.2.5. 事件接收和处理 领域、子域、核心域、通用域和支撑域。 0.1. 领域和子域 领域是从事一种专门活动或事业的范围、部类或部门。 领域具体指一种特定的范围或区域。 领域就是用来确定范围的,范围即边界,这也是 DDD 在设计中不断强调边界的原因。 在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。简言之,DDD 的领域就是这个边界内要解决的业务问题域。 领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。 DDD 的研究方法与自然科学的研究方法类似。 在自然科学研究中遇到复杂问题时,通常的做法就是将问题一步一步地细分,再针对细分出来的问题域,逐个深入研究,探索和建立所有子域的知识体系。当所有问题子域完成研究时,就建立了全部领域的完整知识体系了。 第一步:确定研究对象,即研究领域,一棵桃树。桃树的知识体系是已经确定要研究的问题域,对应 DDD 的领域。 第二步:对研究对象进行细分,将桃树细分为器官。根、茎、叶、花、果实和种子等器官则是细分后的问题子域。这个过程就是 DDD 将领域细分为多个子域的过程。 第三步:将器官细分为组织。这个过程就是 DDD 将子域进一步细分为多个子域的过程。 第四步:将组织细分为细胞,细胞成为研究的最小单元。细胞之间的细胞壁确定了单元的边界,也确定了研究的最小边界。 细胞核、线粒体、细胞膜等物质共同构成细胞,这些物质一起协作让细胞具有这类细胞特定的生物功能。在这里把细胞理解为 DDD 的聚合,细胞内的这些物质就可以理解为聚合里面的聚合根、实体以及值对象等,在聚合内这些实体一起协作完成特定的业务功能。这个过程类似 DDD 设计时,确定微服务内功能要素和边界的过程。 每一个细分的领域都会有一个知识体系,也就是 DDD 的领域模型。在所有子域的研究完成后,我们就建立了全域的知识体系,也就建立了全域的领域模型。 0.1.1. 例子-保险业 为实现保险领域建模和微服务建设,根据业务关联度以及流程边界将保险领域细分为:承保、收付、再保以及理赔等子域,而承保子域还可以继续细分为投保、保全(寿险)、批改(财险)等子子域。 在投保这个限界上下文内可以建立投保的领域模型,投保的领域模型最后映射到系统就是投保微服务。这就是一个保险领域的细分和微服务的建设过程。 领域建模和微服务建设的过程和方法基本类似,其核心思想就是将问题域逐步分解,降低业务理解和系统实现的复杂度。 0.2. 核心域、通用域和支撑域 在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。 核心域:决定产品和公司核心竞争力的子域,是业务成功的主要因素和公司的核心竞争力。 通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能子域。(比如认证、权限等,没有企业特点限制,不需要做太多的定制化。) 支撑域:是必需的功能子域,既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域。(例如数据代码类的数据字典等系统。) 在不同的场景下,不同的人对桃树核心域的理解是不同的,因此对桃树的处理方式也会不一样。园丁更关注桃树花期的营养,而果农则更关注桃树落果期的营养,有时为了保证果实的营养供给,还会裁剪掉疯长的茎和叶(通用域或支撑域)。同样的道理,公司在 IT 系统建设过程中,由于预算和资源有限,对不同类型的子域应有不同的关注度和资源投入策略,好钢要用在刀刃上。 很多公司的业务,表面看上去相似,但商业模式和战略方向是存在很大差异的,因此公司的关注点会不一样,在划分核心域、通用域和支撑域时,其结果也会出现非常大的差异。 在领域细分、建立领域模型和系统建设时,要结合公司战略重点和商业模式,找到且重点关注核心域。 转型微服务架构,要将核心域的建设排在首位且有绝对的掌控能力和自主研发能力,如果资源实在有限的话,可以在支撑域或通用域上,暂时采用外购的方式。 核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。 0.3. 限界上下文 通用语言:定义上下文含义,能够简单、清晰、准确描述业务涵义和规则 限界上下文:定义领域边界,确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。 0.3.1. 通用语言 通用语言包含术语和用例场景,并且能够直接反映在代码中。通用语言中: 名词可以给领域对象命名,如商品、订单等,对应实体对象; 动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。 通用语言贯穿 DDD 的整个设计过程。作为团队沟通和协商形成的统一语言,基于它,能够开发出可读性更好的代码,将业务需求准确转化为代码设计。 从事件风暴建立通用语言到领域对象设计和代码落地的完整过程如下图所示。 在事件风暴的过程中,建立领域模型,在领域建模的过程中会形成通用的业务术语和用户故事。事件风暴也是一个团队统一语言的过程。 通过用户故事分析会形成一个个的领域对象,这些领域对象对应领域模型的业务对象,每一个业务对象和领域对象都有通用的名词术语,并且一一映射。 微服务代码模型来源于领域模型,每个代码模型的代码对象跟领域对象一一对应。 设计过程中可以用表格,来记录事件风暴和微服务设计过程中产生的领域对象及其属性。比如,领域对象在 DDD 分层架构中的位置、属性、依赖关系以及与代码模型对象的映射关系等。 DDD 分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一,在代码模型设计的时侯就要建立领域对象和代码对象的一一映射,从而保证业务模型和代码模型的一致,实现业务语言与代码语言的统一。 0.3.2. 限界上下文(context) 语言都有语义环境,通用语言也有上下文环境。为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD 在战略设计上提出了“限界上下文”这个概念,用来确定语义所在的领域边界。 将限界上下文拆解为两个词:限界和上下文。 限界:是领域的边界 上下文:是语义环境 通过领域的限界上下文,可以在统一的领域边界内用统一的语言进行交流。 限界上下文,用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。 限界上下文是用来细分领域,从而定义通用语言所在的边界。 同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。领域边界就是通过限界上下文来定义的。 0.3.3. 限界上下文与微服务的关系 理论上限界上下文就是微服务的边界。将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。 限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。 除了理论,微服务的拆分还是有很多限制因素的,在设计中不宜过度拆分。 限界上下文划分主要依据是业务的语义边界,比如客户的环境下只说客户相关的事情,权限环境下只定义权限相关的业务逻辑。 对于陌生业务环境,经过分析,基本能够知道有哪些流程和场景,这些流程和场景里应该有对应的语义环境。 而在具体的分析过程中,在确定一个子域,并完成事件风暴后,可以找出实体和聚合,实体和聚合根他们有业务属性和逻辑。基本知道这个聚合可以作什么样的业务,如果多个聚合共同完整这类业务,就可以把多个聚合放在一个限界上下文内,这样一个限界上下文就形成了。 0.4. 实体和值对象 在战略设计向战术设计过渡的这个过程中,理解和区分实体和值对象在不同阶段的形态是很重要的,毕竟阶段不同,它们的形态也会发生变化,这与我们的设计和代码实现密切相关。 0.4.1. 实体 在 DDD 中有这样一类对象,它们拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。 0.4.1.1. 实体的业务形态 在 DDD 不同的设计过程中,实体的形态是不同的。 在战略设计时,实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。 在事件风暴中,可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。 实体和值对象是组成领域模型的基础单元。 0.4.1.2. 实体的代码形态 在代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。在 DDD 里,这些实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑则在领域服务中实现。 0.4.1.3. 实体的运行形态 实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。我们可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。但是,由于它们拥有相同的 ID,它们依然是同一个实体。比如商品是商品上下文的一个实体,通过唯一的商品 ID 来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品。 0.4.1.4. 实体的数据库形态 与传统数据模型设计优先不同,DDD 是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体对象映射到数据库持久化对象。 在领域模型映射到数据模型时,一个实体可能对应 0 个、1 个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。 在某些场景中,有些实体只是暂驻静态内存的一个运行态实体,它不需要持久化。比如,基于多个价格配置数据计算后生成的折扣实体。 而在有些复杂场景下,实体与持久化对象则可能是一对多或者多对一的关系。 比如,用户 user 与角色 role 两个持久化对象可生成权限实体,一个实体对应两个持久化对象,这是一对多的场景。 比如,有些场景为了避免数据库的联表查询,提升系统性能,会将客户信息 customer 和账户信息 account 两类数据保存到同一张数据库表中,客户和账户两个实体可根据需要从一个持久化对象中生成,这就是多对一的场景。 0.4.2. 值对象 《实现领域驱动设计》一书中值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。 在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。当度量和描述改变时,可以用另外一个值对象予以替换。它可以和其它值对象进行相等性比较,且不会对协作对象造成副作用。 值对象本质上就是一个集合。这个集合里面有若干个用于描述目的、具有整体概念和不可修改的属性。 在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎。 0.4.2.1. 举例 人员实体原本包括:姓名、年龄、性别以及人员所在的省、市、县和街道等属性。这样显示地址相关的属性就很零碎。将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”,这个集合就是值对象。 0.4.2.2. 值对象的业务形态 值对象是 DDD 领域模型中的一个基础对象,它跟实体一样都来源于事件风暴所构建的领域模型,都包含了若干个属性,它与实体一起构成聚合。 本质上,实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。 值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。 值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。 在值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的数据类微服务,比如数据字典。 0.4.2.3. 值对象的代码形态 值对象在代码中有这样两种形态。 如果值对象是单一属性,则直接定义为实体类的属性 如果值对象是属性集合,则把它设计为类或结构体,将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用 type Person struct{ id string // 值对象,人员唯一主键 name string // 单一属性值对象 age int // 单一属性值对象 gender bool // 单一属性值对象 address Address // 属性集值对象,被实体引用 } type Address struct{ province string // 值对象,无主键 city string // 值对象 country string // 值对象 street string // 值对象 } 0.4.2.4. 值对象的运行形态 实体实例化后的领域对象的业务属性和业务行为非常丰富 值对象实例化的对象则相对简单和乏味。除了值对象数据初始化和整体替换的行为外,其它业务行为很少 值对象嵌入到实体的话,有这样两种不同的数据格式,也可以说是两种方式,分别是: 属性嵌入的方式:引用单一属性的值对象或只有一条记录的多属性值对象的实体,可以采用属性嵌入的方式嵌入 序列化大对象的方式:引用一条或多条记录的多属性值对象的实体,可以采用序列化大对象的方式嵌入,比如,人员实体可以有多个通讯地址,多个地址序列化后可以嵌入人员的地址属性。 值对象创建后就不允许修改了,只能用另外一个值对象来整体替换。 0.4.2.5. 值对象的数据库形态 DDD 引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变,减少数据库表的数量和表与表之间复杂的依赖关系,尽可能地简化数据库设计,提升数据库性能。 传统的数据建模大多是根据数据库范式设计的,每一个数据库表对应一个实体,每一个实体的属性值用单独的一列来存储,一个实体主表会对应 N 个实体从表。 值对象在数据库持久化方面简化了设计,它的数据库设计大多采用非数据库范式,值对象的属性值和实体对象的属性值保存在同一个数据库实体表中。 基于上述人员和地址那个场景,实体和数据模型设计通常有两种解决方案: 第一是把地址值对象的所有属性都放到人员实体表中,创建人员实体,创建人员数据表,第一个方案会破坏地址的业务涵义和概念完整性 第二是创建人员和地址两个实体,同时创建人员和地址两张表,第二个方案增加了不必要的实体和表,需要处理多个实体和表的关系,从而增加了数据库设计的复杂性 在领域建模时,把地址作为值对象,人员作为实体,保留地址的业务涵义和概念完整性。在数据建模时,将地址的属性值嵌入人员实体数据库表中,只创建人员数据库表。这样既可以兼顾业务含义和表达,又不增加数据库的复杂度。值对象就是通过这种方式,简化了数据库设计。 在领域建模时,将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;在数据建模时,将值对象嵌入实体,减少实体表的数量,简化数据库设计。 有 DDD 专家认为,要想发挥对象的威力,就需要优先做领域建模,弱化数据库的作用,只把数据库作为一个保存数据的仓库即可。即使违反数据库设计原则,也不用大惊小怪,只要业务能够顺利运行,就没什么关系。 0.4.2.6. 值对象的优势与局限 优势是可以简化数据库设计,提升数据库性能 值对象使用不当,优势就会变成劣势 值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。 值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。 0.4.3. 实体与值对象的关系 实体和值对象是微服务底层的最基础的对象,一起实现实体最基本的核心领域逻辑。值对象和实体在某些场景下可以互换,根据团队的设计和开发习惯,以及上面的优势和局限分析,选择最适合的方法。 DDD 提倡从领域模型设计出发,而不是先设计数据模型。传统的数据模型设计通常是一个表对应一个实体,一个主表关联多个从表,当实体表太多的时候就很容易陷入无穷无尽的复杂的数据库设计,领域模型就很容易被数据模型绑架。值对象的诞生,在一定程度上,和实体是互补的。 同样的对象在不同的场景下,可能会设计出不同的结果。 有些场景中,地址会被某一实体引用,它只承担描述实体的作用,并且它的值只能整体替换,这时候可以将地址设计为值对象,比如收货地址。 在某些业务场景中,地址会被经常修改,地址是作为一个独立对象存在的,这时候它应该设计为实体,比如行政区划中的地址信息维护。 0.5. 聚合和聚合根 在事件风暴中,根据业务操作和行为找出实体或值对象,进而将业务关联紧密的实体和值对象进行组合,构成聚合,再根据业务语义将多个聚合划定到同一个限界上下文中,并在限界上下文内完成领域建模。 0.5.1. 聚合 在 DDD 中,实体和值对象是很基础的领域对象。 实体一般对应业务对象,它具有业务属性和业务行为 值对象主要是属性集合,对实体的状态和特征进行描述 实体和值对象都只是个体化的对象,它们的行为表现出来的是个体的能力。 领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。 聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。 聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。 聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。聚合内实体以充血模型实现个体业务能力,以及业务逻辑的高内聚。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。 0.5.2. 聚合根 聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。 如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。 作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。 作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。 在聚合之间,是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。 聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。 0.5.3. 设计聚合 DDD 领域建模通常采用事件风暴,采用用例分析、场景分析和用户旅程分析等方法,通过头脑风暴列出所有可能的业务行为和事件,然后找出产生这些行为的领域对象,并梳理领域对象之间的关系,找出聚合根,找出与聚合根业务紧密关联的实体和值对象,再将聚合根、实体和值对象组合,构建聚合。 判断一个实体是否是聚合根,可以结合以下场景分析: 是否有独立的生命周期? 是否有全局唯一 ID? 是否可以创建或修改其它对象? 是否有专门的模块来管这个实体? 根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。构建出 1 个包含聚合根(唯一)、多个实体和值对象的对象集合,这个集合就是聚合。 在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。 多个聚合根据业务语义和上下文一起划分到同一个限界上下文内。 0.5.3.1. 聚合的设计原则 在一致性边界内建模真正的不变条件:聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。 设计小聚合:如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。 通过唯一标识引用其他聚合:聚合之间是通过关联外部聚合根 ID 的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。 在边界之外使用最终一致性:聚合内数据强一致性,而聚合之间数据最终一致性。在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间的解耦。 通过应用层实现跨聚合的服务调用:为实现微服务内聚合之间的解耦,以及未来以聚合为单位的微服务组合和拆分,应避免跨聚合的领域服务调用和跨聚合的数据库表关联。 0.5.3.2. 聚合特点 高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但不建议对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。 一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。有了这个逻辑边界,在微服务架构演进时就可以以聚合为单位进行拆分和组合,微服务的架构演进也就不再是一件难事了。 聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。 0.5.3.3. 实体的特点 有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一即可。状态可变,它依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。 0.5.3.4. 值对象的特点 无 ID,不可变,无生命周期,用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。值对象尽量只引用值对象。 0.6. 领域事件 在事件风暴时,除了命令和操作等业务行为以外,还有一种非常重要的事件,这种事件发生后通常会导致进一步的业务操作,在 DDD 中这种事件被称为领域事件。 领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。 0.6.1. 识别领域事件 领域事件的定义是强关联的。在做用户旅程或者场景分析时,要捕捉业务、需求人员或领域专家口中的关键词:“如果发生……,则……”“当做完……的时候,请通知……”“发生……时,则……”等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。 聚合的一个设计原则:在边界之外使用最终一致性。一次事务最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的最终一致性。 领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。 有的领域事件发生在微服务内的聚合之间,有的则发生在微服务之间,还有两者皆有的场景,一般来说跨微服务的领域事件处理居多。在微服务设计时不同领域事件的处理方式会不一样。 0.6.1.1. 微服务内的领域事件 当领域事件发生在微服务内的聚合之间,领域事件发生后完成事件实体构建和事件数据持久化,发布方聚合将事件发布到事件总线,订阅方接收事件数据完成后续业务操作。 微服务内大部分事件的集成,都发生在同一个进程内,进程自身可以很好地控制事务,因此不一定需要引入消息中间件。 但一个事件如果同时更新多个聚合,按照 DDD“一次事务只更新一个聚合”的原则,就要考虑是否引入事件总线。 但微服务内的事件总线,会增加开发的复杂度,因此需要结合应用复杂度和收益进行综合考虑。 微服务内应用服务,可以通过跨聚合的服务编排和组合,以服务调用的方式完成跨聚合的访问,这种方式通常应用于实时性和数据一致性要求高的场景。这个过程会用到分布式事务,以保证发布方和订阅方的数据同时更新成功。 0.6.1.2. 微服务之间的领域事件 跨微服务的领域事件会在不同的限界上下文或领域模型之间实现业务协作,其主要目的是实现微服务解耦,减轻微服务之间实时服务访问的压力。 领域事件发生在微服务之间的场景比较多,事件处理的机制也更加复杂。 跨微服务的事件可以推动业务流程或者数据在不同的子域或微服务间直接流转。跨微服务的事件机制要总体考虑事件构建、发布和订阅、事件数据持久化、消息中间件,甚至事件数据持久化时还可能需要考虑引入分布式事务机制等。 微服务之间的访问也可以采用应用服务直接调用的方式,实现数据和服务的实时访问,弊端就是跨微服务的数据同时变更需要引入分布式事务,以确保数据的一致性。 分布式事务机制会影响系统性能,增加微服务之间的耦合,所以还是要尽量避免使用分布式事务。 通过领域事件驱动的异步化机制,可以推动业务流程和数据在各个不同微服务之间的流转,实现微服务的解耦,减轻微服务之间服务调用的压力,提升用户体验。 0.6.2. 领域事件总体架构 领域事件的执行需要一系列的组件和技术来支撑。领域事件总体技术架构如下图,领域事件处理包括: 事件构建和发布 事件数据持久化 事件总线 消息中间件 事件接收和处理等 0.6.2.1. 事件构建和发布 事件基本属性至少包括: 事件唯一标识(全局唯一):使事件能够无歧义地在多个限界上下文中传递 发生时间 事件类型 事件源 事件基本属性主要记录事件自身以及事件发生背景的数据。 还有: 业务属性:用于记录事件发生那一刻的业务数据,这些数据会随事件传输到订阅方,以开展下一步的业务操作 事件基本属性和业务属性一起构成事件实体,事件实体依赖聚合根。 领域事件发生后,事件中的业务数据不再修改,因此业务数据可以以序列化值对象的形式保存,这种存储格式在消息中间件中也比较容易解析和获取。 为了保证事件结构的统一,还会创建事件基类 DomainEvent(参考下图),子类可以扩充属性和方法。由于事件没有太多的业务行为,实现方法一般比较简单。 事件发布之前需要先构建事件实体并持久化。 事件发布的方式有很多种: 通过应用服务或者领域服务发布到事件总线或者消息中间件 从事件表中利用定时程序或数据库日志捕获技术获取增量事件数据,发布到消息中间件 0.6.2.2. 事件数据持久化 事件数据持久化可用于系统之间的数据对账,或者实现发布方和订阅方事件数据的审计。 当遇到消息中间件、订阅方系统宕机或者网络中断,在问题解决后仍可继续后续业务流转,保证数据的一致性。 事件数据持久化有两种方案: 持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性。 持久化到共享的事件数据库中。 注意:业务数据库和事件数据库不在一个数据库中,它们的数据持久化操作会跨数据库,因此需要分布式事务机制来保证业务和事件数据的强一致性,结果就是会对系统性能造成一定的影响。 0.6.2.3. 事件总线 事件总线是实现微服务内聚合之间领域事件的重要组件,它提供事件分发和接收等服务。 事件总线是进程内模型,它会在微服务内聚合之间遍历订阅者列表,采取同步或异步的模式传递数据。 事件分发流程大致如下: 如果是微服务内的订阅者(其它聚合),则直接分发到指定订阅者; 如果是微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到消息中间件; 如果同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到消息中间件。 0.6.2.4. 消息中间件 跨微服务的领域事件大多会用到消息中间件,实现跨微服务的事件发布和订阅。消息中间件的产品非常成熟,市场上可选的技术也非常多,比如 Kafka,RabbitMQ 等。 0.6.2.5. 事件接收和处理 微服务订阅方在应用层采用监听机制,接收消息队列中的事件数据,完成事件数据的持久化后,就可以开始进一步的业务处理。领域事件处理可在领域服务中实现。 领域事件驱动是很成熟的技术,在很多分布式架构中得到了大量的使用。领域事件是 DDD 的一个重要概念,在设计时要重点关注领域事件,用领域事件来驱动业务的流转,尽量采用基于事件的最终一致,降低微服务之间直接访问的压力,实现微服务之间的解耦,维护领域模型的独立性和数据一致性。 领域事件驱动机制可以实现一个发布方 N 个订阅方的模式,这在传统的直接服务调用设计中基本是不可能做到的。
0.1. 用户接口层 0.2. 应用层 0.3. 领域层 0.4. 基础层 0.5. DDD 分层架构如何推动架构演进 0.5.1. 微服务架构的演进 0.5.2. 微服务内服务的演进 0.6. 三层架构演进到 DDD 分层架构 0.7. 常见模型对比分析 0.7.1. 整洁架构 0.7.2. 六边形架构 0.7.3. 三种模型架构对比 0.7.4. 从三种架构模型看中台和微服务设计 0.7.4.1. 中台建设聚焦领域模型 0.7.4.2. 微服务要有合理的架构分层 0.7.4.2.1. 项目级微服务 0.7.4.2.2. 企业级中台微服务 0.7.4.3. 应用与资源的解耦与适配 微服务架构模型有好多种,例如: 整洁架构 CQRS(Command Query Responsibility Segregation) 六边形架构 虽然每种架构模式提出的时代和背景不同,但其核心理念都是为了设计出“高内聚低耦合”的架构,轻松实现架构演进。而 DDD 分层架构的出现,使架构边界变得越来越清晰,它在微服务架构模型中,占有非常重要的位置。 DDD 的分层架构在不断发展。 最早是传统的四层架构; 后来四层架构有了进一步的优化,实现了各层对基础层的解耦; 再后来领域层和应用层之间增加了上下文环境(Context)层,五层架构(DCI)就此形成了。 在传统四层架构中,基础层是被其它层依赖的,它位于最核心的位置,按照分层架构的思想,它就是核心,但实际上领域层才是软件的核心,所以这种依赖是有问题的。后来采用依赖倒置(Dependency inversion principle,DIP)的设计,优化了传统的四层架构,实现了各层对基础层的解耦。 本文的 DDD 分层架构就是优化后的四层架构。在下面这张图中,从上到下依次是:用户接口层、应用层、领域层和基础层。 0.1. 用户接口层 用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。 0.2. 应用层 应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。 应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。 在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。 因为庞大的应用层会使领域模型失焦,时间一长你的微服务就会演化为传统的三层架构,业务逻辑会变得混乱。 应用服务是在应用层的,它负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,以粗粒度的服务通过 API 网关向前端发布。 应用服务还可以进行安全认证、权限校验、事务控制、发送或订阅领域事件等。 0.3. 领域层 领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。 领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。 领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。 实体和领域服务在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。 0.4. 基础层 基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。 比较常见的功能是提供数据库持久化。 基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。 在传统架构设计中,由于上层应用对数据库的强耦合,很多公司在架构演进中最担忧的可能就是换数据库了,因为一旦更换数据库,就可能需要重写大部分的代码,这对应用来说是致命的。 采用依赖倒置的设计以后,应用层就可以通过解耦来保持独立的核心业务逻辑。当数据库变更时,只需要更换数据库基础服务,这样就将资源变更对应用的影响降到了最低。 0.5. DDD 分层架构如何推动架构演进 领域模型不是一成不变的,因为业务的变化会影响领域模型,而领域模型的变化则会影响微服务的功能和边界。那该如何实现领域模型和微服务的同步演进? 0.5.1. 微服务架构的演进 领域模型中对象的层次从内到外依次是:值对象、实体、聚合和限界上下文。 实体或值对象的简单变更,一般不会让领域模型和微服务发生大的变化。但聚合的重组或拆分却可以。这是因为聚合内业务功能内聚,能独立完成特定的业务逻辑。那聚合的重组或拆分,势必就会引起业务模块和系统功能的变化了。 以聚合为基础单元,完成领域模型和微服务架构的演进。聚合可以作为一个整体,在不同的领域模型之间重组或者拆分,或者直接将一个聚合独立为微服务。 0.5.2. 微服务内服务的演进 在微服务内部,实体的方法被领域服务组合和封装,领域服务又被应用服务组合和封装。在服务逐层组合和封装的过程中,你会发现这样一个有趣的现象。 在服务设计时,并不一定能完整预测有哪些下层服务会被多少个上层服务组装,因此领域层通常只提供一些原子服务,比如领域服务 a、b、c。但随着系统功能增强和外部接入越来越多,应用服务会不断丰富。 当领域服务 b 和 c 同时多次被多个应用服务调用了,执行顺序也基本一致。可以考虑将 b 和 c 合并,再将应用服务中 b、c 的功能下沉到领域层,演进为新的领域服务(b+c)。这样既减少了服务的数量,也减轻了上层服务组合和编排的复杂度。 这就是服务演进的过程,它是随着系统发展的,最后领域模型会越来越精炼,越来越能适应需求的快速变化。 0.6. 三层架构演进到 DDD 分层架构 DDD 分层架构的优势: 首先,由于层间松耦合,可以专注于本层的设计,而不必关心其它层,也不必担心自己的设计会影响其它层。DDD 成功地降低了层与层之间的依赖。 其次,分层架构使得程序结构变得清晰,升级和维护更加容易。修改某层代码时,只要本层的接口参数不变,其它层可以不必修改。即使本层的接口发生变化,也只影响相邻的上层,修改工作量小且错误可以控制,不会带来意外的风险。 传统企业应用大多是单体架构,而单体架构则大多是三层架构。三层架构解决了程序内代码间调用复杂、代码职责不清的问题,但这种分层是逻辑概念,在物理上它是中心化的集中式架构,并不适合分布式微服务架构。 DDD 分层架构中的要素其实和三层架构类似,只是在 DDD 分层架构中,这些要素被重新归类,重新划分了层,确定了层与层之间的交互规则和职责边界。 三层架构向 DDD 分层架构演进,主要发生在业务逻辑层和数据访问层。 DDD 分层架构在用户接口层引入了 DTO,给前端提供了更多的可使用数据和更高的展示灵活性。 DDD 分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。DDD 分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化,领域层实现领域模型的能力。 三层架构数据访问采用 DAO 方式;DDD 分层架构的数据库等基础资源访问,采用了仓储(Repository)设计模式,通过依赖倒置实现各层对基础资源的解耦。仓储又分为两部分:仓储接口和仓储实现。仓储接口放在领域层中,仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config 等通用的公共的资源类统一放到了基础层。 传统三层架构向 DDD 分层架构的演进,体现的正是领域驱动设计思想的演进。 0.7. 常见模型对比分析 0.7.1. 整洁架构 整洁架构又名“洋葱架构”,像洋葱片一样,它体现了分层的设计思想。在整洁架构里,同心圆代表应用软件的不同部分,从里到外依次是: 领域模型 领域服务 应用服务 最外围的容易变化的内容,比如用户界面和基础设施 整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。 外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。 在洋葱架构中,各层的职能是这样划分的: 领域模型:实现领域内核心业务逻辑,它封装了企业级的业务规则。领域模型的主体是实体,一个实体可以是一个带方法的对象,也可以是一个数据结构和方法集合。 领域服务:实现涉及多个实体的复杂业务逻辑。 应用服务:实现与用户操作相关的服务组合与编排,它包含了应用特有的业务流程规则,封装和实现了系统所有用例。 最外层:主要提供适配的能力,适配能力分为主动适配和被动适配。 主动适配主要实现外部用户、网页、批处理和自动化测试等对内层业务逻辑访问适配。 被动适配主要是实现核心业务逻辑对基础资源访问的适配,比如数据库、缓存、文件系统和消息中间件等。 红圈内的领域模型、领域服务和应用服务一起组成软件核心业务能力。 0.7.2. 六边形架构 六边形架构又名“端口适配器架构”。 六边形架构的核心理念是:应用是通过端口与外部进行交互的。这也是微服务架构下 API 网关盛行的主要原因吧。 在六边形架构中,红圈内的核心业务逻辑(应用程序和领域模型)与外部资源(包括 APP、Web 应用以及数据库资源等)完全隔离,仅通过适配器进行交互。它解决了业务逻辑与用户界面的代码交错问题,很好地实现了前后端分离。六边形架构各层的依赖关系与整洁架构一样,都是由外向内依赖。 六边形架构将系统分为内六边形和外六边形两层,这两层的职能划分如下: 红圈内的六边形实现应用的核心业务逻辑; 外六边形完成外部应用、驱动和基础资源等的交互和访问,对前端应用以 API 主动适配的方式提供服务,对基础资源以依赖倒置被动适配的方式实现资源访问。 六边形架构的一个端口可能对应多个外部系统,不同的外部系统也可能会使用不同的适配器,由适配器负责协议转换。这就使得应用程序能够以一致的方式被用户、程序、自动化测试和批处理脚本使用。 0.7.3. 三种模型架构对比 图中红色线框的作用就是将核心业务逻辑与外部应用、基础资源进行隔离。 红色框内部主要实现核心业务逻辑,但核心业务逻辑也是有差异的: 有的业务逻辑属于领域模型的能力, 有的则属于面向用户的用例和流程编排能力。 按照这种功能的差异,在这三种架构中划分了应用层和领域层,来承担不同的业务逻辑。 领域层实现面向领域模型,实现领域模型的核心业务逻辑,属于原子模型,它需要保持领域模型和业务逻辑的稳定,对外提供稳定的细粒度的领域服务,所以它处于架构的核心位置。 应用层实现面向用户操作相关的用例和流程,对外提供粗粒度的 API 服务。就像一个齿轮一样进行前台应用和领域层的适配,接收前台需求,随时做出响应和调整,尽量避免将前台需求传导到领域层。应用层作为配速齿轮则位于前台应用和领域层之间。 这三种架构都考虑了前端需求的变与领域模型的不变。 需求变幻无穷,但变化总是有矩可循的,用户体验、操作习惯、市场环境以及管理流程的变化,往往会导致界面逻辑和流程的多变。但总体来说,不管前端如何变化,在企业没有大的变革的情况下,核心领域逻辑基本不会大变,所以领域模型相对稳定,而用例和流程则会随着外部应用需求而随时调整。 把握好这个规律,就知道该如何设计应用层和领域层。 架构模型通过分层的方式来控制需求变化从外到里对系统的影响,从外向里受需求影响逐步减小。面向用户的前端可以快速响应外部需求进行调整和发布,灵活多变,应用层通过服务组合和编排来实现业务流程的快速适配上线,减少传导到领域层的需求,使领域层保持长期稳定。 这样设计的好处,可以保证领域层的核心业务逻辑不会因为外部需求和流程的变动而调整,对于建立前台灵活、中台稳固的架构很有帮助。 中台和微服务设计的关键是:领域模型和微服务的合理分层设计。 0.7.4. 从三种架构模型看中台和微服务设计 中台本质上是领域的子域,它可能是核心域,也可能是通用域或支撑域。通常认为阿里的中台对应 DDD 的通用域,将通用的公共能力沉淀为中台,对外提供通用共享服务。 中台作为子域还可以继续分解为子子域,在子域分解到合适大小,通过事件风暴划分限界上下文以后,就可以定义微服务了,微服务用来实现中台的能力。表面上看,DDD、中台、微服务这三者之间似乎没什么关联,实际上它们的关系是非常紧密的,组合在一起可以作为一个理论体系用于中台和微服务设计。 0.7.4.1. 中台建设聚焦领域模型 中台需要站在全企业的高度考虑能力的共享和复用。 中台设计时,需要建立中台内所有限界上下文的领域模型,DDD 建模过程中会考虑架构演进和功能的重新组合。领域模型建立的过程会对业务和应用进行清晰的逻辑和物理边界(微服务)划分。领域模型的结果会影响到后续的系统模型、架构模型和代码模型,最终影响到微服务的拆分和项目落地。因此,在中台设计中首先要聚焦领域模型,将它放在核心位置。 0.7.4.2. 微服务要有合理的架构分层 微服务设计要有分层的设计思想,让各层各司其职,建立松耦合的层间关系。 不要把与领域无关的逻辑放在领域层实现,保证领域层的纯洁和领域逻辑的稳定,避免污染领域模型。 不要把领域模型的业务逻辑放在应用层,这样会导致应用层过于庞大,最终领域模型会失焦。如果实在无法避免,可以引入防腐层,进行新老系统的适配和转换,过渡期完成后,可以直接将防腐层代码抛弃。 有的微服务可以与前端应用集成,一起完成特定的业务,这是项目级微服务。 有的则是某个职责单一的中台微服务,企业级的业务流程需要将多个这样的微服务组合起来才能完成,这是企业级中台微服务。 两类微服务由于复杂度不一样,集成方式也会有差异。 0.7.4.2.1. 项目级微服务 项目级微服务的内部遵循分层架构模型。 领域模型的核心逻辑在领域层实现,服务的组合和编排在应用层实现,通过 API 网关为前台应用提供服务,实现前后端分离。 项目级的微服务可能会调用其它微服务,通常项目级微服务之间的集成 ,发生在微服务的应用层,由应用服务调用其它微服务发布在 API 网关上的应用服务。 下图中微服务 B 中红色框内的应用服务 B,它除了可以组合和编排自己的领域服务外,还可以组合和编排外部微服务的应用服务。它只要将编排后的服务发布到 API 网关供前端调用,这样前端就可以直接访问自己的微服务了。 0.7.4.2.2. 企业级中台微服务 企业级的业务流程往往是多个中台微服务一起协作完成的,企业级中台微服务的集成不能像项目级微服务一样,在某一个微服务内完成跨微服务的服务组合和编排。可以在中台微服务之上增加一层,如下面增加的这一层就位于红色框内,它的主要职能就是处理跨中台微服务的服务组合和编排,以及微服务之间的协调,还可以完成前端不同渠道应用的适配。 如果再将业务范围扩大一些,可以将它做成一个面向不同行业和渠道的服务平台。BFF(后端前置,Backend for Frontends) 微服务与其它微服务存在较大的差异,就是它没有领域模型,因此这个微服务内也不会有领域层。 BFF 微服务可以承担应用层和用户接口层的主要职能,完成各个中台微服务的服务组合和编排,可以适配不同前端和渠道的要求。 0.7.4.3. 应用与资源的解耦与适配 传统以数据为中心的设计模式,应用会对数据库、缓存、文件系统等基础资源产生严重依赖。正是由于它们之间的这种强依赖的关系,一旦更换基础资源就会对应用产生很大的影响,因此需要为应用和资源解耦。 在微服务架构中,应用层、领域层和基础层解耦是通过仓储模式,采用依赖倒置的设计方法来实现的。在应用设计中,会同步考虑和基础资源的代码适配,那么一旦基础设施资源出现变更(比如换数据库),就可以屏蔽资源变更对业务代码的影响,切断业务逻辑对基础资源的依赖,最终降低资源变更对应用的影响。
0.1. 平台 0.2. 中台 0.3. 前中后台协同 0.3.1. 前台 0.3.2. 中台 0.3.3. 后台 0.4. DDD、中台和微服务如何协作 0.4.1. 中台建模 0.1. 平台 平台只是将部分通用的公共能力独立为共享平台。虽然可以通过 API 或者数据对外提供公共共享服务,解决系统重复建设的问题,但这类平台并没有和企业内的其它平台或应用,实现页面、业务流程和数据从前端到后端的全面融合,并且没有将核心业务服务链路作为一个整体方案考虑,各平台仍然是分离且独立的。 平台解决了公共能力复用的问题,但离中台的目标显然还有一段差距! 0.2. 中台 阿里对中台的定义:“中台是一个基础的理念和架构,把所有的基础服务用中台的思路建设,进行联通,共同支持上端的业务。业务中台更多的是支持在线业务,数据中台提供了基础数据处理能力和很多的数据产品给所有业务方去用。业务中台、数据中台、算法中台等等一起提供对上层业务的支撑。” 思特沃克对中台的定义:“中台是企业级能力复用平台。” 联通是前台以及中台之间的联通 融合是前台流程和数据的融合 并以共享的方式支持前端一线业务的发展和创新 中台首先体现的是一种企业级的能力,它提供的是一套企业级的整体解决方案,解决小到企业、集团,大到生态圈的能力共享、联通和融合问题,支持业务和商业模式创新。通过平台联通和数据融合为用户提供一致的体验,更敏捷地支撑前台一线业务。 中台来源于平台,但中台和平台相比,它更多体现的是一种理念的转变,它主要体现在这三个关键能力上:对前台业务的快速响应能力;企业级复用能力;从前台、中台到后台的设计、研发、页面操作、流程服务和数据的无缝联通、融合能力。 其中最关键的是快速响应能力和企业级的无缝联通和融合能力,尤其是对于跨业经营的超大型企业来说至关重要。 相对互联网企业而言,传统企业的渠道应用更多样化,有面向内部人员的门店类应用、面向外部用户的互联网电商以及移动 APP 类应用。这些应用面向的用户和场景可能不同,但其功能类似,基本涵盖了核心业务能力。此外,传统企业也会将部分核心应用的页面或 API 服务能力开放给生态圈第三方,相互借力发展。 为了适应不同业务和渠道的发展,过去很多企业的做法是开发很多独立的应用或 APP。但由于 IT 系统建设初期并没有企业级的整体规划,平台之间融合不好,就导致了用户体验不好,最关键的是用户并不想装那么多 APP。 为了提升用户体验,实现统一运营,很多企业开始缩减 APP 的数量,开始通过一个 APP 集成企业内的所有能力,联通前台所有的核心业务链路。 由于传统企业的商业模式和 IT 系统建设发展的历程与互联网企业不是完全一样的,因此传统企业的中台建设策略与阿里中台战略也应该有所差异,需要共享的内容也不一样。 由于渠道多样化: 传统企业不仅要将通用能力中台化,以实现通用能力的沉淀、共享和复用,这里的通用能力对应 DDD 的通用域或支撑域; 传统企业还需要将核心能力中台化,以满足不同渠道的核心业务能力共享和复用的需求,避免传统核心和互联网不同渠道应用出现“后端双核心、前端两张皮”的问题,这里的核心能力对应 DDD 的核心域。 这就属于业务中台的范畴了,我们需要解决核心业务链路的联通和不同渠道服务共享的问题。 除此之外,还需要解决系统微服务拆分后的数据孤岛、数据融合和业务创新等问题,这就属于数据中台的范畴了,尤其是当我们采用分布式架构以后,就更应该关注微服务拆分后的数据融合和共享问题了。 综上,在中台设计和规划时,需要整体考虑企业内前台、中台以及后台应用的协同,实现不同渠道应用的前端页面、流程和服务的共享,还有核心业务链路的联通以及前台流程和数据的融合、共享,支持业务和商业模式的创新。 0.3. 前中后台协同 企业级能力往往是前中后台协同作战能力的体现。 如果把业务中台比作陆军、火箭军和空军等专业军种的话,它主要发挥战术专业能力。前台就是作战部队,它需要根据前线的战场需求,对业务中台的能力进行调度,实现能力融合和效率最大化。 而数据中台就是信息情报中心和联合作战总指挥部,它能够汇集各种数据、完成分析,制定战略和战术计划。后台就是后勤部队,提供技术支持。 0.3.1. 前台 传统企业的早期系统有不少是基于业务领域或组织架构来建设的,每个系统都有自己的前端,相互独立,用户操作是竖井式,需要登录多个系统才能完成完整的业务流程。 中台化后的前台建设要有一套综合考虑业务边界、流程和平台的整体解决方案,以实现各不同中台前端操作、流程和界面的联通、融合。不管后端有多少个中台,前端用户感受到的就是只有一个前台。 在前台设计中可以借鉴微前端的设计思想,在企业内不仅实现前端解耦和复用,还可以根据核心链路和业务流程,通过对微前端页面的动态组合和流程编排,实现前台业务的融合。 前端页面可以很自然地融合到不同的终端和渠道应用核心业务链路中,实现前端页面、流程和功能复用。 0.3.2. 中台 传统企业的核心业务大多是基于集中式架构开发的,而单体系统存在扩展性和弹性伸缩能力差的问题,因此无法适应忽高忽低的互联网业务场景。而数据类应用也多数通过 ETL 工具抽取数据实现数据建模、统计和报表分析功能,但由于数据时效和融合能力不够,再加上传统数据类应用本来就不是为前端而生的,因此难以快速响应前端一线业务。 业务中台的建设可采用领域驱动设计方法,通过领域建模,将可复用的公共能力从各个单体剥离,沉淀并组合,采用微服务架构模式,建设成为可共享的通用能力中台。同样的,可以将核心能力用微服务架构模式,建设成为可面向不同渠道和场景的可复用的核心能力中台。 业务中台向前台、第三方和其它中台提供 API 服务,实现通用能力和核心能力的复用。 在将传统集中式单体按业务职责和能力细分为微服务,建设中台的过程中,会产生越来越多的独立部署的微服务。这样做虽然提升了应用弹性和高可用能力,但由于微服务的物理隔离,原来一些系统内的调用会变成跨微服务调用,再加上前后端分离,微服务拆分会导致数据进一步分离,增加企业级应用集成的难度。 如果没有合适的设计和指导思想,处理不好前台、中台和后台的关系,将会进一步加剧前台流程和数据的孤岛化、碎片化。 数据中台的主要目标是打通数据孤岛,实现业务融合和创新,包括三大主要职能: 一是:完成企业全域数据的采集与存储,实现各不同业务类别中台数据的汇总和集中管理。 二是:按照标准的数据规范或数据模型,将数据按照不同主题域或场景进行加工和处理,形成面向不同主题和场景的数据应用,比如客户视图、代理人视图、渠道视图、机构视图等不同数据体系。 三是:建立业务需求驱动的数据体系,基于各个维度的数据,深度萃取数据价值,支持业务和商业模式的创新。 相应的,数据中台的建设就可分为三步走: 第一步:实现各中台业务数据的汇集,解决数据孤岛和初级数据共享问题。 第二步:实现企业级实时或非实时全维度数据的深度融合、加工和共享。 第三步:萃取数据价值,支持业务创新,加速从数据转换为业务价值的过程。 数据中台不仅限于分析型场景,也适用于交易型场景。它可以建立在数据仓库或数据平台之上,将数据服务化之后提供给业务系统。基于数据库日志捕获的技术,使数据的时效性大大提升,这样就可以为交易型场景提供很好的支撑。 综上,数据中台主要完成数据的融合和加工,萃取数据业务价值,支持业务创新,对外提供数据共享服务。 0.3.3. 后台 前台:主要面向客户以及终端销售者,实现营销推广以及交易转化; 中台:主要面向运营人员,完成运营支撑; 后台:主要面向后台管理人员,实现流程审核、内部管理以及后勤支撑,比如采购、人力、财务和 OA 等系统。 对于后台,为了实现内部的管理要求,很多人习惯性将这些管理要求嵌入到核心业务流程中。而一般来说这类内控管理需求对权限、管控规则和流程等要求都比较高,但是大部分管理人员只是参与了某个局部业务环节的审核。这类复杂的管理需求,会凭空增加不同渠道应用前台界面和核心流程的融合难度以及软件开发的复杂度。 在设计流程审核和管理类功能的时候,可以考虑按角色或岗位进行功能聚合,将复杂的管理需求从通用的核心业务链路中剥离,参考小程序的建设模式,通过特定程序入口嵌入前台 APP 或应用中。 管理需求从前台核心业务链路剥离后,前台应用将具有更好的通用性,它可以更加容易地实现各渠道前台界面和流程的融合。一个前台应用或 APP 可以无差别地同时面向外部互联网用户和内部业务人员,从而促进传统渠道与互联网渠道应用前台的融合。 企业的中台转型不只是中台的工作,需要整体考虑前台、中台和后台的协同、共享、联通和融合。前台通过页面和流程共享实现不同渠道应用之间的前台融合,中台通过 API 实现服务共享。而前台、业务中台和数据中台的融合可以实现传统应用与互联网应用的融合,从而解决“后端双核心、前端两张皮”的问题。能力复用了,前台流程和数据融合了,才能更好地支持业务的融合和商业模式的创新。 0.4. DDD、中台和微服务如何协作 中台是抽象出来的业务模型,微服务是业务模型的系统实现,DDD 作为方法论可以同时指导中台业务建模和微服务建设,三者相辅相成,完美结合。 DDD 有两把利器,那就是它的战略设计和战术设计方法。 中台在企业架构上更多偏向业务模型,形成中台的过程实际上也是业务领域不断细分的过程。在这个过程中会将同类通用的业务能力进行聚合和业务重构,再根据限界上下文和业务内聚的原则建立领域模型。而 DDD 的战略设计最擅长的就是领域建模。 在中台完成领域建模后,就需要通过微服务来完成系统建设。此时,DDD 的战术设计又恰好可以与微服务的设计完美结合。中台和微服务正是 DDD 实战的最佳场景。 中台的本质其实就是提炼各个业务板块的共同需求,进行业务和系统抽象,形成通用的可复用的业务模型,打造成组件化产品,供前台部门使用。前台要做什么业务,需要什么资源,可以直接找中台,不需要每次都去改动自己的底层。 传统企业可以将需要共享的公共能力进行领域建模,建设可共享的通用中台,还会将核心能力进行领域建模,建设面向不同渠道的可复用的核心中台。这里的通用中台和核心中台都属于业务中台的范畴。 DDD 的子域分为核心域、通用域和支撑域。划分这几个子域的主要目的是为了确定战略资源的投入,一般来说战略投入的重点是核心域,因此后面可以暂时不严格区分支撑域和通用域。 领域、中台以及微服务虽然属于不同层面的东西,但还是可以将他们分解对照,整理出来它们之间的关系。下面这张图从 DDD 领域建模和中台建设这两个不同的视角对同一个企业的业务架构进行分析。 从领域功能属性和重要性对照来看,通用中台对应 DDD 的通用域和支撑域,核心中台对应 DDD 的核心域。从领域的功能范围来看,子域与中台是一致的。领域模型所在的限界上下文对应微服务。建立了映射关系,就可以用 DDD 来进行中台业务建模。 保险域的业务中台分为两类: 第一类是:提供保险核心业务能力的核心中台(比如营销、承保和理赔等业务); 第二类是:支撑核心业务流程完成保险全流程的通用中台(比如订单、支付、客户和用户等)。 根据 DDD 首先要建立通用语言的原则,在将 DDD 的方法引入中台设计时,要先建立中台和 DDD 的通用语言。这里的子域与中台是一致的,那就可以将子域统一为中台。 中台通过事件风暴可以进一步细分,最终完成业务领域建模。中台业务领域的功能不同,限界上下文的数量和大小就会不一样,领域模型也会不一样。 当完成业务建模后,就可以采用 DDD 战术设计,设计出聚合、实体、领域事件、领域服务以及应用服务等领域对象,再利用分层架构模型完成微服务的设计。 以上就是 DDD、中台和微服务在应用过程中的协作模式。 0.4.1. 中台建模 中台业务抽象的过程就是业务建模的过程,对应 DDD 的战略设计。 系统抽象的过程就是微服务的建设过程,对应 DDD 的战术设计。 结合 DDD 领域建模的方法,进行中台业务建模的过程。 第一步:按照业务流程(通常适用于核心域)或者功能属性、集合(通常适用于通用域或支撑域),将业务域细分为多个中台,再根据功能属性或重要性归类到核心中台或通用中台。核心中台设计时要考虑核心竞争力,通用中台要站在企业高度考虑共享和复用能力。 第二步:选取中台,根据用例、业务场景或用户旅程完成事件风暴,找出实体、聚合和限界上下文。依次进行领域分解,建立领域模型。由于不同中台独立建模,某些领域对象或功能可能会重复出现在其它领域模型中,也有可能本该是同一个聚合的领域对象或功能,却分散在其它的中台里,这样会导致领域模型不完整或者业务不内聚。这里先不要着急,这一步我们只需要初步确定主领域模型就可以了,在第三步中我们还会提炼并重组这些领域对象。 第三步:以主领域模型为基础,扫描其它中台领域模型,检查并确定是否存在重复或者需要重组的领域对象、功能,提炼并重构主领域模型,完成最终的领域模型设计。 第四步:选择其它主领域模型重复第三步,直到所有主领域模型完成比对和重构。 第五步:基于领域模型完成微服务设计,完成系统落地。 DDD 战略设计包括上述的第一步到第四步,主要为:业务域分解为中台,对中台归类,完成领域建模,建立中台业务模型。DDD 战术设计是第五步,领域模型映射为微服务,完成中台建设。 以保险领域为例的话,完成领域建模后,里面的数据就可以填上了。这里选取通用中台的用户、客户和订单三个中台来做示例。 客户中台提炼出了两个领域模型:客户信息和客户视图模型。 用户中台提炼出了三个领域模型:用户管理、登录认证和权限模型。 订单中台提炼出了订单模型。