业务架构就是把业务要素及要素之间的关系进行结构化表达、并给出了其设计和演化的原则与指南的一套框架。关于业务架构请看我前面发的文章»业务架构的概念和作用

DDD 全称是 Domain-Driven Design,中文叫领域驱动设计,是一套应对复杂软件系统分析和设计的面向对象建模方法论。

今天我们聊聊业务架构与DDD是如何融合在一起,输出相应的落地方法的。~

图片

1 DDD的背景

  • DDD的主要元素介绍;

  • DDD的实现过程;

  • DDD与业务架构协同。

图片

1.1 关于DDD的发展

2004年,软件大师Eric Evans发表了名著Domain-driven Design:Tackling Complexity in the Heart of Software,正式定义了领域驱动设计(Domain Driven Design,DDD)的概念,开启了DDD的时代,但DDD在前十年似乎一直不温不火,并没有在全球广泛流行。

2013年,Vaughn Vernon写了一本Implementing Domain-Driven Design,进一步明确了DDD 的领域方向,并给出了很多落地指导,给了DDD的流行一些助力。

DDD真正开始流行,是在2014年。这一年,Martin Fowler写了Microservices,通俗易懂地讲解了什么是微服务架构。由于微服务具备灵活部署、可扩展、技术异构等优点,一改以往单体应用为多个子应用,在互联网领域得到了广泛的应用,一时间大家拆服务拆得不亦乐乎。但软件没有“银弹”,微服务也带来了开发和运维复杂性等问题。很多项目因为前期拆分过度,导致服务爆炸,复杂度过高,后期难以运维甚至难以上线。人们在踩过服务过度拆分的坑后,开始反思,业务或者微服务的边界到底在什么地方?如何确定边界?人们苦苦寻觅,发现原来DDD就是解决之道,“DDD+微服务”就是复杂系统设计的完整解决方案,从此DDD开始真正广泛应用和流行。

1.2 DDD是解决微服务边界划分的良方

面对复杂问题,解决办法通常是分而治之,即通过拆分、模块化,化整为零。领域驱动建模 DDD面向业务层面,对业务领域的划分和整合,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解、难以演进的问题。

而微服务架构是面向实现及开发应用所使用的一种架构,它将大型应用分解成多个独立的组件,其中每个组件都有各自的责任领域。在处理一个用户请求时,基于微服务的应用可能会调用许多内部微服务来共同完成该请求。

所以总体而言,DDD是设计思想,属于逻辑层面;微服务是实现技术,属于物理层面。DDD的设计输出物“领域模型及划分的限界上下文",是微服务的输入。一个限界上下文对应一个微服务,解决微服务的业务和应用边界划分的难题,同时解决微服务落地时的设计难题。由此可见,DDD与微服务是复杂系统设计不同阶段所使用的工具、技术或方法,二者结合起来围绕一个共同业务目标,实现业务复用及业务需求的快速响应。

2 DDD的相关元素介绍

DDD体系涉及一定数量的元素,本节在提供整体概览以后,会逐一介绍相关元素以期大家对DDD相关元素有一个快速了解:

  • 领域;

  • 子域;

  • 限界上下文;

  • 实体;

  • 值对象;

  • 聚合;

  • 聚合根;

    通用语言。

2.1相关元素概览

DDD 涉及一些元素,为便于大家理解相关概念,我们会结合图9-1来简单介绍DDD所涉及的主要元素及其相互关系。从图1中可以看出基本的层次关系:领域 → 子域 → 限界上下文 → 聚合 → 实体/值对象。不同层次上的主要元素及相关的一些扩展概念,下面也会分别进行介绍。

图片

图1 DDD的主要元素及其相互关系

2.2 领域

DDD 的领域,就是某个业务边界里面要解决的业务问题域。在分析和解决业务问题时,按照约定的规则,对业务领域进行细分。当业务领域细分到一定的颗粒度后,DDD就将问题范围限定在了特定的边界内。然后在这个边界内建立领域模型,最后用代码实现该领域模型,解决相应的业务问题。这种将问题逐级细分,来降低业务和系统的复杂度,也是DDD讨论的核心。但业务领域拆分的具体原则,DDD并没有给出明确的指导,本书将在后面推荐一种拆分原则。

2.3 子域

领域可以进一步划分成子领域,即子域。每个子域都对应一个更小的问题域或更小的业务范围,这样可以分离业务和技术实现的复杂性。如针对制造行业采购领域,采购可以作为领域,采购需求管理、寻源管理、供应商管理、采购订单等可以作为不同的子域。

子域可以根据自身的重要性和功能属性归属到三类子域:核心域、通用域、支撑域。

1. 核心域

决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。技术团队应该把核心域建设放在首位,即使外购系统,一般也具备对其很强的二次开发能力,如采购领域下的采购需求管理、寻源管理、供应商管理等子域。

2. 通用域

没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域,比如权限认证。

3. 支撑域

支撑域具备企业特点,但不是具备通用功能的子域,它就是支撑域。比如电子制造行业中,大部分公司对二、三级供应商管理都是非常简单甚至缺失的,多级(二、三级甚至更多级)供应商管理对其而言就是一个支撑域。支撑域在特定情况下也有可能转变为核心域,比如华为公司由于受到美国制裁,多级供应商管理会成为其核心域。

2.4 界限上下文

限界上下文可以拆分为两部分来理解。

  • “限界”指其确定了领域模型的业务边界,同时确定了开发团队的工作边界、系统架构的应用。

  • “上下文”指其提供上下文语境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

理论上,限界上下文的边界就是微服务的边界,因此,理解限界上下文在设计中非常重要。

2.5 实体

在DDD的实践过程中,是先识别出实体再划分聚合的。DDD概念中的聚合,其本质是实体的组合,聚合的颗粒度大于实体。但如果不先介绍“实体”,很难说清楚“聚合”,因而我们在此会先介绍实体的概念,再介绍聚合的概念。

在以往的软件系统设计中,我们习惯于将关注点放在数据上,而非领域上。这样就导致我们在软件开发过程中会首先做数据库的设计,实体对象则是根据数据库表结构设计进行转换的结果。

而在DDD中,实体的设计是从底层的子域出发的,这样数据就天然具备了边界,方便后续的微服务拆分。DDD中的实体要求是唯一的且是可持续变化的,这里的唯一性要求其拥有唯一标识,且该标识在历经各种状态变更后仍能保持一致。可变性也正反映了实体本身的状态和行为。另外,领域模型中的实体是多个属性、操作或行为的载体,一般是在事件风暴中,根据命令、操作或者事件,找出产生这些行为的实体对象。实体以领域对象的形式存在,每个实体对象都有唯一的ID。一个实体对象可以进行多次修改,修改后的数据和原来的数据可能会大不相同,但其拥有相同的ID,它们依然是同一个实体。如供应商基本信息实体,包含了供应商编码、供应商名称、供应商等级等信息,供应商等级等信息可以发生变化,但供应商编码作为其唯一业务ID是不发生变化的。

2.6 值对象

值对象的核心是将多个相关属性组合为一个固定的整体概念。实体和值对象都是领域模型的成员,实体是业务唯一性的载体,是富对象,包含业务逻辑和唯一标识。而值对象是属性的集合,没有唯一标识,只是数据的容器,没有业务逻辑。如供应商基本信息中会包含供应商联系方式,但联系方式可以有移动电话、座机、传真等,有的供应商可能还不止一个座机或传真等联系方式。如图2所示,在传统范式建模中,供应商联系方式会作为一个独立的逻辑实体而存在;而在DDD的领域建模中,会识别出联系方式这个值对象,它是各种联系方式属性的集合,其属性若有变化,就替换为另外一个值对象。值对象一般以属性值方式或者序列化对象JSON 的方式呈现。

图片

图2 DDD实体与值对象

值对象作为实体的一部分,符合通用语言,更简明地表达简单业务概念,也可以简化设计,减少不必要的数据库表设计,能够提升系统性能。但值对象过多会导致业务的缺失,影响查询性能。如按照省份、城市统计供应商分布,值对象则不易使用。所以具体哪些属性可以作为值对象存在,需要具体问题具体分析。

2.7 聚合

本节会以“采购”业务为例,介绍“聚合”涉及的相关概念及关系,如图3所示。

图片

图3聚合的相关概念及关系

领域中的实体对象和值对象不是孤立存在的,往往几个对象的组合才能表示一个完整的概念,比如采购订单PO,是通过订单头和订单行实体组合在一起表达的。因此聚合是由业务和逻辑紧密关联的实体和值对象组合而成的,用来封装业务并保证聚合内领域对象的数据一致性。所以聚合也是数据持久化的基本单元,每一个聚合都对应一个仓储,实现数据的持久化。

但聚合之间的边界是松耦合的。按照这种方式设计出来的微服务很自然地就是“高内聚、低耦合”的。跨聚合内多个实体的业务逻辑通过聚合提供的服务来实现,跨多个聚合的业务逻辑通过应用层服务来实现。

聚合内某些对象的状态必须满足某个业务规则。

  • 一个聚合只有一个聚合根,聚合根是可以独立存在的,聚合中其他实体或值对象依赖于聚合根。

  • 只有聚合根才能被外部访问到,聚合根维护聚合的内部一致性。

2.8 聚合根

聚合中的一组实体是一个团队,聚合根就是这个团队的主管。聚合根也被称为根实体,它不仅是实体,还是聚合的管理者。如采购订单PO这个聚合中,包含了采购订单头实体、采购订单行实体、地址值对象,其中采购订单头实体是采购订单这个聚合的聚合根。

聚合根在数据库中相当于主表的概念,实体则是一般的表,而值对象依托引用的实体表被设计成嵌入属性集或者以JSON串的形式存储。一个聚合只有一个聚合根,聚合根是可以独立存在的,聚合中其他实体或值对象依赖于聚合根。只有聚合根才能被外部访问到,聚合根维护整个聚合的内部一致性。聚合之间通过聚合根ID关联引用;如果需要访问其他聚合的实体,就要先访问该聚合的聚合根,再通过聚合根导航到该聚合的其他实体,外部对象不能直接访问某个聚合的非根实体。

2.9 通用语言

通用语言作为DDD里的一个核心术语,在此有必要被单独提出。DDD的方法,本身就是业务和 IT 的各种角色一起共创的方法,而通用语言就是团队交流中达成共识的,能够简单、清晰、准确地描述业务涵义和规则的语言。如采购领域中,外购物料有制造商和供应商的概念,需要大家达成一致理解。比如一颗日本TDK公司的电容,TDK是供应商还是制造商?如果TDK直供给我们,那么TDK既是制造商又是供应商;但如果是通过渠道分销商购买的,那么制造商是TDK,供应商是渠道分销商。

通用语言的目的是创造可以被业务、技术和代码自身无歧义使用的共同术语,解决语言含义的不统一,这样不论是业务人员、产品经理、技术负责人、ScrumMaster,还是其他什么角色,每个人都使用统一的语言和统一的定义来交流。通用语言包含术语和用例场景,除了在需求文档和设计文档中出现,还能够直接反映在代码中。

2.10  DDD的基本实现过程

对于一个产品的DDD实现过程,大致如图4所示。

图片

图4 DDD实现过程

实现过程主要包括4个主要环节。

**(1)需求澄清:**这里的需求澄清,并不是说需要直接输出一个需求的PRD文档,而是针对了解需求的背景,确定讨论的参与者,为后面的事件风暴做准备。

**(2)领域场景分析:**领域专家与产品经理、开发人员、测试人员通过头脑风暴的形式,从用户视角出发,根据业务流程或用户旅程,探讨出领域事件、实体和命令等领域对象。

**(3)领域建模:**居于领域场景分析的输出,分析产生命令的实体,并根据实体之间的依赖关系形成聚合。然后为聚合划定限界上下文,建立领域模型及模型之间的依赖。

**(4)代码映射与落地:**建立领域对象和代码对象的映射关系,包含聚合、领域服务、应用服务、仓储接口等与包名、类名、方法名的映射关系,然后进行代码实现。

3 DDD与业务架构等的融合

3.1 DDD如何在企业范围内发挥作用

DDD 从业务视角出发,通过领域模型不仅解决了复杂系统的边界问题,实现合适颗粒度的高内聚和低耦合,而且DDD也从实现视角出发,给出了领域模型的实现架构,细化到数据实体、领域服务、代码级别。但DDD的相关应用实践往往聚焦在某个事业都或特定业务领域,如果上升到更高层面,即将问题空间扩大到超过问题域和系统范围层面,变成企业范围或整个集团层面之后,DDD的模式就显得捉襟见肘了,DDD并未明确说明领域的划分原则及领域如何与公司业务结合。

正如前几章讲到的,TOGAF等给出了企业架构的框架,但其在具体落地中存在3个现实问题。

**(1)架构复杂:**现有的企业架构总体过于复杂,架构元素过多,大部分人听到这么多名词就头大,很难完全掌握全貌与细节,导致无法有效落地。

**(2)缺乏元素关联:**大家常常在谈论4A架构,但是4A往往变成4个独立的视图,4个A之间该如何有机结合缺乏适合的方法。

**(3)缺乏战术指导:**利用企业架构的思想和方法完成设计后,如何保证方案的落地?当前企业架构偏向中高阶设计,并没有给出一种适合特定企业的具体落地方法,无法保证其能够真正落地,最后往往成为一堆文档,束之高阁。

而 DDD 给出了领域模型的具体实现思路,可以和企业架构的架构元素进行有机结合:我们可将业务架构的设计作为DDD战略部分设计的输入,并结合DDD的战术设计进行代码实现。通过这种结合,就能真正有效地在企业里把战略和战术设计串联起来,进而实现企业整体设计到具体落地实施的打通。

3.2 DDD与业务架构、数据架构、应用架构的融合

1. 业务架构中业务能力映射到DDD中的领域与子域

DDD中对于领域如何划分并没有给出明确的指导,而业务架构包含业务流程和业务能力这两大视角,彼此可以相互融合。不同产品所涉及的业务线或不同业务场景的闭环落地,都会涉及端到端流程,这些端到端流程可能都会调用相同的业务能力。对于不同产品线,比如电脑产品和网络产品,都会涉及供应链能力。对于不同业务场景,比如生产采购和非生产采购在流程细节上有差异,但是都会包含供应商管理能力,供应商管理能力展开后又包含供应商认证管理、供应商选择管理、供应商绩效管理等下一级能力,而这些不同层级的能力可以作为领域和子域划分的重要参考。

业务架构可与DDD进行优势互补、协同落地,如图5所示。

图片

图5业务架构与DDD协同落地

2. 数据架构中业务主题拉齐到聚合的颗粒度

数据架构中包含了主题域-主题组-主题-逻辑实体,其中主题在设计时往往是基于业务认知去抽取的大颗粒度的对象,是一堆逻辑实体的集合。如产品开发和制造都用到“物料”(Part或者 Item)这个实体,“物料”包含了物料基本信息和物料属性两个逻辑实体,而物料基本信息实体又包含物料分类,物料分类又会引出物料分类属性逻辑实体(如图9-6所示,但为了方便理解,简化掉了物料版本带来的差异信息)。

而 DDD 中的聚合实质上也是一种逻辑上的设计,但聚合这个集合的划分颗粒度既考虑了业务领域划分的事件,也考虑了一定实现视角,其对聚合中实体的访问,要求通过根实体进行。从实践经验看,我们可以直接将“聚合”的划分原则应用到数据“主题”的识别原则中,此时就可以直接拉通DDD中的聚合与数据架构中的主题。如图6所示,物料聚合拉通物料主题,物料分类聚合也等同于物料分类主题(注:图中聚合模型做了简化,实际的实体数量会更多)。

图片

图6聚合与数据架构中的主题拉通

3. 应用架构中应用以限界上下文作为基本划分依据

在应用架构中,应用或者子应用(如有)是一个可以独立发布和部署的单元,每个应用都可以提供一个或者多个服务。而限界上下文作为微服务逻辑划分的依据,也是部署的单元,因此在大部分场景下,限界上下文同样可以作为应用划分的基础依据。另外,在制造行业中,其实大部分应用系统都面向内部用户,用户规模、最大并发都是比较确定的,大部分都不需要分布式架构,因此应用会以相对较大的颗粒度来划分,以减少系统调用,降低部署的复杂性。

这样,通过能力域<–>领域,应用<–>限界上下文,主题<–>聚合,我们就能较好地将企业架构中的元素与DDD的方法进行结合,做到真正的架构落地,如图7所示。

图片

图7BA、DA、AA与DDD 协同落地