聊一聊中台和DDD(领域驱动设计)

本次分享价值:本次分享主要针对中台、微服务和领域模型的理念、本质及其构建方法论进行探讨。对领域分析的价值所在就是寻求“千变万化”中相对的“稳定性、第一性”,然后通过合理的架构分析及抽象隔离业务的复杂度和技术复杂度,隔离业务领域的稳定性和易变性,从架构上精巧、快速的支撑业务的变化。


#中台到底是什么?


中台的概念最早是从阿里流传出来的,阿里的中台战略是从业务中台和数据中台开始的,采用业务中台和数据中台相结合的双中台建设模式。后来也有人提出技术中台,AI中台等等。


在阿里完美落地中台之后,很多企业开始对阿里对标。完成大一统的集中式系统拆分,实现了从传统应用向大平台的演进。但是每个企业对中台的理解,也都不一样,**那么阿里的中台到底是什么样的?**


阿里业务中台的前身是共享平台[https://cloud.tencent.com/developer/article/1780514],而原来的共享平台更多的是被当作资源团队,承接各业务方的需求,并为业务方在基础服务上做定制开发。阿里业务中台的目标是把核心服务链路(会员、商品、交易、营销、店铺、资金结算等)整体当做一个平台产品来做,为前端业务提供的是业务解决方案,而不是独立的系统。这种能力有别于传统的烟囱的系统建设方式。


**说到这里,那么中台到底是什么呢?**


其实不同人不同团队对于中台的定位和理解是存在很大争议的。


我们先看下阿里对中台的定义:中台是一个基础的理念和架构,我们要用中台的思想建设、联通所有基础服务,共同支持上端的业务。业务中台更多的是支持在线业务,数据中台则提供基础数据处理能力和很多的数据产品供所有业务方使用。即业务中台、数据中台和算法中台等一起提供对上层业务的支撑。


我们可以看到关于中台的几个关键词:共享、联通、融合和创新。


联通是前台和中台之间各业务版本的联通。


融合是前台企业级业务流程和数据的融合,并以共享的方式支持前台一线业务的发展和创新。


其实我对中台的理解就是,中台首先体现的是一种企业级的能力,他提供的是一套企业级的整体解决方案,解决小到企业、集团,大到生态圈的能力共享、业务联通和融合的问题,支持业务和商业模式的创新。通过平台联通、业务和数据融合,为前台用户提供一致体验,更敏捷的职称前台一线业务。


中台来源平台,但于平台相比,中台更多的是一种理念的转变,它主要体现在这三个关键的能力上。


1、对前台业务的快速响应能力。


2、企业级的复用能力。


3、从前台、中台到后台的设计、研发、页面操作、流程、服务和数据的无缝联通、融合的能力。


#微服务设计为什么要选择DDD?


其实最近几年微服务架构的思想越来越普及,很多企业已经或者尝试从单体架构向微服务架构转型。微服务也成为很多中大型企业实施中台战略的不二之选。但是在微服务实施过程中有很多问题,单体应用到底应该如何去拆分微服务?边界到底怎么划分?微服务这个微字到底如何衡量,到底拆成到什么粒度合适?微服务应该如何设计?对于这类问题,不同团队不同人都有自己的经验和对微服务的理解,各执一词,那么有没有适合的理论或者设计方法来知道微服务设计呢?


## 软件架构的演进史


软件的架构模式大体来说经历了从单机、集中式到分布式微服务架构三个阶段的演进。



第一阶段:单机架构,这个阶段通常采用面向过程的设计方法,通常采用C/S架构,大多采用结构化编程方式,


第二阶段:集中式架构,这个阶段通常采用面向对象的设计方法。一般采用经典的三层架构,系统包括业务接入层、业务逻辑层和数据库层。这种设计模式往往容易使系统变得臃肿,可扩展性和弹性伸缩能力差。


第三阶段:分布式微服务架构,该架构可以是实现业务和应用之间的解耦,解决集中式单体应用扩展性和弹性伸缩能力不足的问题,更加适合云计算环境下的部署和运营。


##微服务拆分和设计的困境


微服务架构的引入,的确解决了单体应用的很多问题,比如扩展性、弹性伸缩能力、小规模团队的敏捷开发等。但是在微服务实践过程中,也产生了不少争论和疑惑,比如微服务的粒度如何把握?微服务到底如何拆分和设计呢?微服务的边界到底应该在哪里?


可以说,很久以来都没有一套系统的理论和方法来知道微服务的拆分和设计。微服务拆分困境产生的根本原因,就是不知道业务或者应用的边界到底在什么地方。换句话说,如果确定了业务边界和应用边界,这个困境也就迎刃而解的。


其实有时候微服务设计的重点不在于微服务的大小,也不在于拆分了多少个微服务,而是在于微服务内部的边界是否清晰,这些边界是否进行了有效的隔离,以及这些微服务上线后能否随着业务的发展轻松实现业务模型和微服务架构的演进,所以,在微服务设计时,我们要考虑微服务拆分的大小,也要关注微服务内部的逻辑边界。


微服务设计强调从业务领域出发,因此我们第一步要做的就是先划分业务的领域边界,然后在这个边界内构建业务的领域模型,根据领域模型来完成从单体应用到微服务的建设。


那么如何确定业务和应用的边界?是否有理论或知识体系来知道呢?


那就是DDD!


DDD也就是领域驱动设计,2003年Eric·Evans出版了《领域驱动设计》这本书之后,DDD诞生。DDD的核心思想就是从业务角度出发,根据限界上下文划分业务的领域边界,定义领域模型,确认业务边界。在微服务落地时,建立业务领域模型和微服务代码模型的映射关系,从而保证业务架构和微服务系统架构的一致性。但DDD提出后在软件开发领域一直都是雷声大,雨点小。直到Martin Fowler提出微服务架构后,DDD才迎来了自己的时代。


## 为什么DDD适合微服务


首先DDD是一种处理高度复杂领域的设计思想,一种结构设计方法,它试图分离技术实现的复杂性,并围绕业务概念构建领域模型来控制业务的复杂性,以解决软件难以理解,难以演进的问题。


而微服务是一种架构风格,两者从本质上都是为了追求软件的高响应力,从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务领域触发,根据业务的发展,合理规划业务领域边界,采用分治策略,降低业务和软件开发的复杂度,持续调整现有架构,优化现有代码,以保持架构和代码的生命力,也就是我们常说的演进式架构。


那为什么说DDD适合微服务呢?我们可以通过DDD战略和战术设计方法,划定业务领域边界,构建领域模型,用领域模型指导微服务拆分和设计,解决微服务的业务和应用边界难以划分的难题,同事解决微服务落地时设计的难题。


另外,更关键的一点是,DDD不仅可以指导微服务的边界划分和设计,也可以很好地应用于企业中台的领域建模设计,帮你建立一个边界清晰、可高度复用的企业级中台业务模型,完成微服务的落地。


那是不是说,只有微服务系统才使用用DDD呢?


DDD不仅适用于微服务拆分和设计,同样也适用于单体应用。如果单体应用采用了DDD方法设计,当某一天你想将单体应用拆分为多个微服务时,你会发现采用DDD 方法设计出来的单体应用,拆分起来比采用传统三层架构设计出来的单体应用容易很多。这是因为用DDD方法设计的单体应用,在应用内部会有很多聚合,聚合之间是松耦合的,但聚合内部的功能具有高内聚的特点。有了这一层清晰的聚合边界,我们就可以很容易完成从单体应用向微服务的拆分了。


# DDD、中台和微服务的关系


DDD和微服务源于西方,而中台来自于阿里,三者看起风马牛不相及,实则缘分匪浅。中台是抽象出来的业务模型,微服务是业务模型的系统实现,DDD作为方法论可以同时知道中台业务建模和微服务建设,三者相辅相成,完美结合。


你可能会疑惑,为什么DD都可以同时指导中台和微服务建设呢?这是因为DDD有两把利器,那就是它的战略设计和战术设计方法。


![image-20210726191646742](/Users/wangzhongyuan/Library/Application Support/typora-user-images/image-20210726191646742.png)


中台在企业架构上更多的是偏向业务架构,形成中台的过程实际上也是业务领域不断细化和能力沉淀的过程。在这个过程中我们会对同类通用的业务能力进行聚合和重构,再根据限界上下文和业务内聚的原则建立领域模型。DDD战略设计最擅长的就是领域建模。


在中台完成领域建模后,DDD战略设计构建的领域模型就可以作为微服务设计的输入。此时,限界上下文和领域模型可以作为微服务拆分和设计的边界和依据,所以,DDD的战术设计又恰好可以与微服务设计完美无缝结合。


可以说,业务中台和微服务正是DDD实战的最佳场景。


#DDD的基本原理


在DDD的知识体系里有很多概念,比如领域、子域、核心子域、通用子域、支撑子域、限界上下文、聚合、聚合根、实体、值对象、领域服务和应用服务等。他们在DDD理论和知识体系里都是非常重要的概念。


## 什么是领域和子域


领域,名词解释是“领域是从事一种专门活动或事业的范围、部类或部门”,在DDD中领域就是用来确定范围的,范围即边界。


在研究和解决业务问题时,DDD会按照一定的规则对业务领域进行细分,当领域细分到一定的程度后,DDD会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决响应的业务问题。简而言之,DDD的领域就是这个边界内要解决的业务问题域。


子域,既然领域是用来限定业务边界和范围的,那么就会有大小之分,领域越大,业务边界的范围就越大,泛指则相反。领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或者更小的业务范围。


## 什么是限界上下文


在DDD领域建模过程中,会有很多项目参与者,不同人对同样的领域知识会有不同的理解。而且有时候同一领域内的名词和术语也可能不统一,团队成员交流起来会出现障碍,严重时甚至会传达错误的信息。


在DDD中有“通用语言”和“限界上下文”这两个重要概念。两者相辅相成,通用语言用于定义上下文对象的含义,而限界上下文则用于定于领域边界,以确保每个上下文对象在它特定的边界内具有唯一的定义,在这个边界内,组合这些对象构建领域模型。


什么是通用语言:通过团队交流达成共识的,能够简单、清晰、准确地描述业务含义和规则的语言就是通用语言。通用语言中的名词一般可以给领域对象命名,比如货源、订单等,他们对应领域模型中的实体对象。而动词则表示一个动作或领域事件,如果下单,订单支付,它们对应领域模型中的领域事件或者命令。


限界上下文:为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD在战略设计上提出了“限界上下文”这个概念,用来确定语义所以的领域边界。“限界”是指具体的领域边界,而“上下文”则是业务语义所在的上下文环境。


定义:限界上下文就是在限定的上下文环境内,用来封装通用语言和领域对象,保证领域内的一些术语、领域对象等有一个确切的含义,没有语义二义性的一个业务边界。


举个例子,企业在设置组织架构时,就是在定义企业的限界上下文边界。而且往往会从企业的职能边界出发,根据这些只能边界来设置部门,划定部门的边界,比如:可以为人力资源管理相关设置人力资源部,为财务核算管理相关的职能设置财务部门等等。


## 什么是实体和值对象


DDD战术设计中有两个重要的概念:实体(Entity)和值对象,两个都是领域模型中非常重要的基础领域对象。


实体:在DDD的领域模型中有这样的一类对象,它们拥有唯一标识符,并且他们的标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是属性,而是其延续性和标识,这种对象的延续性和标识会跨越升值超过软件的生命周期。我们把领域模型中这样的领域对象成为实体。


在代码模型中,实体的表现形式就是实体类。这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务行为和业务逻辑。这些实体通常采用充血模型,与实体相关的所有业务逻辑都在实体类方法中实现,跨多个实体的领域逻辑则在领域服务中实现。


值对象:相对实体而言,值对象会更加抽象一些,我们来看下《实现驱动领域设计》中对值对象的定义。值对象是通过对象属性值来识别的对象,它将多个相关属性组合成一个概念整体,用于描述领域的某个特定方面,并且是一个没有标识符的对象。


也就是说,值对象描述了领域中的某一个东西,这个东西是不可变的,它将不同的关联属性组合成了一个概念整体。举个例子人员实体:包括姓名,年龄,性别及人员所在的省、市、县和街道等属性。这样在人员实体中,显示地址的多个属性就会显得很零碎。所以,我们把“省、市、县和街道”等属性拿出来,构成一个地址的属性集合,这个属性集合的名称就是地址值对象。


![image-20210726212142445](/Users/wangzhongyuan/Library/Application Support/typora-user-images/image-20210726212142445.png)


## 什么是聚合和聚合根


在DDD中实体和值对象是很基础的领域对象。但实体和值对象知识个体化的业务对象,他们所表现出来的是个体的行为和能力,在领域模型中我们需要一个这样的组织,将这些紧密关联的个体对先聚集在一起,按照组织内统一的业务规则共同完成特定的业务功能,因此就有了聚合的概念


聚合:领域模型内的实体和值对象就类似这些组织中的个体,而能让实体和值对象协同工作的组织就是聚合。换句话说,从技术的角度,聚合是有业务和逻辑紧密关联的实体和值对象组合而成的。


聚合根:聚合内有一定的业务规则以确保聚合内数据的一致性,如果在实现业务逻辑时,任由服务对聚合内实体数据进行修改,那么很可能会因为在数据变更过程中失去统一的业务规则控制,而导致聚合内实体之间数据逻辑的不一致。所以引入了聚合根,聚合根的主要目的是避免聚合内由于复杂数据模型缺少统一的业务规则控制,而导致聚合内实体和值对象等领域之间数据不一致的问题。也就是说,在聚合根的方法或领域服务中可以用上这些业务规则,来确保聚合内数据变更时可以保持数据逻辑一致性。


如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根就是这个组织的负责人。聚合根也称为根实体,但它不仅是实体,还是聚合的管理者。


##什么是领域事件


在领域建模时,我们发现除了命令和操作等业务行为以外,还有一类非常重要的事件。这类事件发生后通常会触发进一步的业务操作,在DDD中这类事件被称为领域事件。


举例来说,领域事件可以是业务流程的一个步骤,比如下单成功就会去触发下架货源,也可以是定时触发的事件,也可以是一个事件发生后触发的后续动作等等。


领域事件采用事件驱动架构设计,可以切断领域模型之间的依赖关系,在领域事件发布后,事件发布方不必关心订阅方的事件处理是否成功。这样就可以实现领域模型的解耦,维护领域模型的独立性。当领域模型映射到微服务时,领域事件就可以解耦微服务,这时微服务之间的数据就可以不再要求强一致,而是基于最终一致性。


## 小结


聚合、聚合根、实体和值对象是领域层聚合内非常重要的领域对象,他们之间具有很强的关联性。聚合里包括聚合根。实体、值对象和领域服务等,他们共同按照聚合的业务规则完成聚合的核心领域逻辑。


总结下他们之间的联系和区别。


聚合的特点:聚合内部业务逻辑高内聚,聚合之间满足低耦合的特点。聚合是领域模型中最小的业务逻辑边界。聚合是领域模型中可拆分为微服务的最小单位,可以按照聚合独立拆分成一个微服务,但主要不要过度拆分,这样会增加运维和集成成本。


聚合根的特点:聚合根是实体,有实体的特点,拥有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象通过对象引用的方式进行组织和协调,聚合与聚合之间只能通过聚合根ID引用的方式,实现聚合支架的访问和协同。


实体的特点:实体有ID标识,通过ID判断相等性,ID在聚合内唯一即可。实体的状态可变,他依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与持久化对象不是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。


值对象的特点:值对象没有ID,且数据不可变,他没有生命周期,用完即扔。值对象通过属性值判断相等性。它是一组概念完成的属性组成的集合,用户描述实体的状态和特征,其核心本质是“值”。


# DDD的分层架构


微服务的架构模型有好多种,有洋葱架构,CQRS和六边形架构等。虽然这些架构模式提出的时代和背景不同,但其核心理念都是为了设计出“高内聚,低耦合”的微服务。DDD分层架构的出现,使微服务的架构边界变得越来越清晰,在微服务架构模型中,占用非常重要的位置,那么到底DDD的分层架构时什么样的?


我们看下DDD架构的发展与演进。



用户接口层常见解释是这样的,用户接口层负责向yoghurt展示信息的和解析用户指令,这里的用户可能是用户、程序、自动化测试和批处理脚本等。


用户接口层主要facade接口、DTO以及DO数据的组装和转换等代码逻辑。主要作用就是避免暴露微服务的业务逻辑,导致数据外泄,不能将后端对象的所有属性数据,不加区分的暴露给所有前端应用。更不能因为前端应用不同的数据展示需求,而在后端定制开发出多个不同的应用服务或领域服务,这样会产生大量重复代码,也容易导致业务逻辑混乱,代码可维护性变差。


应用层:连接用户接口层和领域层,他是很薄的一层,主要职能就是协调领域层多个聚合完成服务的组合和编排。但切记不要将本应该在领域层的核心领域逻辑在应用层时间,这会使得领域模型失焦,时间一长就会导致应用层和领域层的边界变得混乱,边界清晰的四层架构满满就可能演变成业务逻辑复杂的三层架构了。


领域层:领域层位于应用层之下,是领域模型的核心,主要实现领域模型的核心业务逻辑,体现领域模型的业务能力。领域层用于表达业务概念、业务状态和业务规则,可以通过各种业务规则校验手段保证业务的正确性。领域层主要关注实现领域对象或者聚合自身的原子业务逻辑,不太关注外部用户操作或者流程等方面的业务逻辑。


基础层:基础层贯穿了DDD所有层,它的主要职能就是为其他各层提供通用的技术和基础服务,包括第三方服务,驱动,消息中间件,网关,文件,缓存以及数据库等。


重要原则:每层只能与其下方的层发生耦合。


# 中台领域和微服务建设方法论


## 如何用事件风暴构建领域模型


前面我们说过微服务设计为什么要选择DDD,最重要的一点就是可以用DDD方法建立的领域模型,清晰地划分微服务逻辑边界和物理边界。


那么我们应该采用什么样的方法,去构建领域模型呢?下面就在DDD战略设计中常使用的一种方法:事件风暴


事件风暴是在2013年由老外提出来的,事件风暴是一项团队活动,领域专家与项目团队通过头脑风暴的形式,罗列出领域中所有的领域事件、整合之后形成最终的领域事件集合,然后,为每一个事件标注出导致该事件的命令,再为每一个事件标注出命令发起方的角色。命令可以是用户发起,也可以是第三方系统调用或者定时器触发等,最后对事件进行分类,整理出实体、聚合、聚合根以及限界上下文等,在限界上下文边界内构件领域模型。


另外事件风暴过程也是建立团队通用语言的过程,这个过程对项目团队确定项目建设目标,完成业务领域模型分析,系统建设和落地非常重要。


领域建模的关键过程包括:产品愿景分析、场景分析、领域建模、微服务拆分与设计等几个重要阶段。

上一篇:Java进阶专题(二) 软件架构设计原则(上)


下一篇:小程序动态添加可响应的数组