DDD与微服务的关系

DDD 兴起

我们都知道这些年随着设备以及技术的发展,软件架构发生了很多变化,从最初的单机(BS/CS)架构到后面的集中式架构,再到如今的微服务架构, 现在基本可以说是微服务架构盛行的时代, DDD早在2004年就由埃里克·埃文斯提出, 但一直处于一个不愠不火的状态,直到Martin Fowler的《Microservices》引起大家注意, 也就是微服务盛行之后(这儿需要说明的是,微服务最早的提出者不是Martin Fowler,而是Fred George), DDD再次回到人们视野中间,为什么呢 ?

我们先看一下三种技术架构的演进以及主要区别:
在这里插入图片描述
第一阶段是单机架构,特征是整个开发围绕着数据库进行设计和开发。

第二个阶段是三层式的集中式架构,采用面向对象的设计方法,业务逻辑分业务层、逻辑层、数据访问层,这种架构很容易某一层或者几层变得臃肿,扩展性较差, 另外摩尔定律失效, 单台机器性能有限。

第三层阶段是微服务架构,在集中式架构中, 系统分析、设计和开发往往是独立进行的,而且各个阶段负责人可能不一样,那么就涉及到交流信息丢失的问题, 另外项目从分析到开发经历的流程很长,很容易最终开发设计与需求实现的不一样,微服务主要就是解决第二阶段的这些痛点,实现应用之间的解耦,解决单体应用扩展性的问题。

微服务存在的问题

进入微服务之后 , 解决了集中式架构的单体应用很多问题, 但是新的问题应运而生 , 微服务的粒度应该多大 ?微服务如何设计呢?微服务如何拆分 ?微服务边界在哪里 ?

很长时间人们都没有解决这一问题,就连Martin Fowler在提出微服务架构的时候也没有告诉我们这该如何拆分微服务。

甚至在很长的时间里人们对微服务拆分产生了一些误解, 有人认为:“微服务很简单,就是将之前的单体应用拆分成多个部署包, 或者将原来的单体应用架构替换为一套支持微服务的技术架构,就算是微服务了。” 还有人认为微服务应该拆分得越小越好。

鉴于上述情形, 很多项目因为前期拆分过度, 导致复杂度过高, 导致后期难以运维甚至难以上线。

可以得出一个结论:微服务拆分困境产生的根本原因就是不知道业务或者微服务的边界到底在什么地方。换句话说,确定了业务边界和应用边界,这个困境也就迎刃而解了

而DDD就是解决了这个确定业务边界的问题,可见DDD并不是一种技术架构,而是一种划分业务领域范围的方法论。DDD的兴起是由于很多熟悉领域驱动建模(DDD)的工程师在进行微服务设计时, 发现用DDD的思路进行业务梳理可以很好规划服务边界, 可以很好实现微服务内部和外部的高内聚、低耦合。于是越来越多的人将DDD作为业务划分的指导思想。

DDD 概述

DDD是一种拆解业务、划分业务、确定业务边界的方法, 是一种高度复杂的领域设计思想,将我们的问题拆分成一个个的域, 试图分离技术实现的复杂性,主要解决的是软件难以理解难以演进的问题,DDD不是一种架构, 而是一种架构方法论, 目的就是将复杂问题领域简单化, 帮助我们设计出清晰的领域和边界, 可以很好的实现技术架构的演进。

1、领域驱动设计基于领域建模而非数据建模
上面例子中,代码重构前activity实体只有基本的属性和get/set方法,即“失血模型”,从而导致activity这个领域对象退化为数据对象,只用作orm组件的crud,失血模型在项目代码中随处可见。究其原因,跟对象-关系映射(ORM,比如hibernate)持久化机制的流行是有直接关系的,使用ORM将每个类映射到一张数据表,通过实体对象完成crud,久而久之实体成为了orm框架的专用名词,即丧失了领域能力。进行项目设计时,我们应该从业务领域角度出发思考问题,而不应该从数据库角度,我们将在战略设计部分详细讨论。

2、满足六边形架构设计
六边形架构在后文进行详细介绍,洋葱架构、干净架构与六边形架构类似。
满足以上两点,并对DDD的一些概念进行映射实践,那么你的系统已经符合DDD了。总结,DDD不是一套全新的特殊架构,是任何项目代码经过重构,满足高可维护性、高可扩展性、高可测试性、代码结构清晰之后必将达到的终点。

DDD包括两部分,战略设计部分和战术设计部分

战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。

战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

DDD战略设计会建立领域模型 , 领域模型用来指导微服务的设计和拆分, DDD第一步要做的就是来一个头脑风暴, 可以理解成一起讨论对业务的理解 , 主要目的就是尽可能前面不遗漏的分解我们的业务领域, 就好比刚刚的桃树, 最先要做的就是尽可能多的分析, 确保每一个领域都可以被关注到, 在实践中 ,往往会采用用例分析、场景分析和用户旅程分析, 这是一个发散的过程,头脑风暴阶段会产生很多实体、命令、事件等领域对象, 我们从不同的维度对进行聚类形成聚合、限界上下文等边界,建立领域模型, 这是一个收敛的过程。

在这里插入图片描述

具体来说, 我们可以通过三步来确定领域模型和微服务边界。

第一步:在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。

第二步:根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。在这个图里,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界,所以用虚线表示。

第三步:根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。在这个图里,限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。

上面除了领域、聚合、实体外还出现了聚合根、值对象等词语, 除此之外还有统一建模语言、子域、核心域、通用域、支撑域等, 其实都是纸老虎, 前面说过了为了方便对于某个领域的讨论往往会形成一些概念,这些概念会有一些名词概括, 上面的名词就是这么来的, 这就叫统一建模语言 , 哈哈哈 ,好了,不扯淡, 我后面的文章会解释下这些词语的意思, 这儿主要讨论DDD解决什么了问题。

梳理一下DDD与微服务的关系, DDD 是一种架构设计方法,微服务是一种架构风格,两者从本质上都是为了追求高响应力,而从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务出发,其核心要义是强调根据业务发展,合理划分领域边界,持续调整现有架构,优化现有代码,以保持架构和代码的生命力,也就是我们常说的演进式架构。DDD 主要关注:从业务领域视角划分领域边界,构建通用语言进行高效沟通,通过业务抽象,建立领域模型,维持业务和代码的逻辑一致性。微服务主要关注:运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署

DDD 战略设计

战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。
领域: 从广义上讲,领域即是一个组织所做的事情以及其中包含的一切。每个公司或组织都有它自己的业务范围和做事方式,这个业务范围以及其在其中所进行的活动便是领域。当你为某个公司开发软件时,你面对的便是这个公司的领域。这个领域对于你来说应该是明确的,因为你在这个领域中工作。
对于打赏这种业务,打赏本身便是领域,即打赏领域。无论你的打赏对象是一位主播、一部电影或者一篇博文,又无论你的打赏道具是RMB、虚拟币、火箭等等,打赏都是这个领域的核心。
子域: 对于领域模型包含“领域”这个词,我们可能会认为整个业务系统创建一个单一的、内聚的、功能全能式的模型。然后这并不是我们使用DDD的目标。正好相反,在DDD中,一个领域被分成若干小的域,这些若干的小的域即子域。事实上,在开发一个领域模型时,我们关注的通常只是这个业务系统的某个方面,对领域的拆分将有助于我们成功。
限界上下文: 一个由显示边界限定的特殊职责。领域模型存在于边界之内,在边界内,每一个模型概念,包括它的属性和操作,都具有特殊的含义。

架构分层: 分层架构的一个重要原则是——每层只能与位于其下方的层发生聚合。
在这里插入图片描述

为了实现接口的定义与实现解耦,接口定义在领域层,实现定义在基础设施层。但这样违背了由顶至底的单项依赖原则。为了解决这个问题,我们此处采用依赖倒置原则,依赖倒置原则内容——高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。根据此原则,结构调整如下:
在这里插入图片描述

我们将基础设施层放在所有层的最上方,这样它可以实现所有其它层定义的接口。

当我们在分层架构中采用依赖倒置原则时,我们可能会发现,事实上已经不存在分层的概念了。无论是高层还是底层,它们都只依赖于抽象,好像把整个分层推平了一样。推平之后,客户通过“平等”的方式与系统交互。加入新的客户也只是不同的输入、输出,以及不同的展现形式而已,这既是我们即将了解的另一个架构,六边形架构。

在这里插入图片描述

六边形架构

在我们的代码中,有很多直接的外部依赖和实现细节。如mybatis的mapper类、httpclient注入、rocketmq的监听、缓存的直接操作等等。这样的实现有两个比较明显问题,一是当底层更换基础组件时对业务逻辑有直接影响,更换代码改动量及测试范围大大增加。二是不利于功能的复用,如果其他业务有类似逻辑,做不到直接移植复用。
2005年Alistair Cockburn提出了六边形架构,又被称为端口和适配器架构。观察上图我们发现,对于核心的应用程序和领域模型来说,其他的底层依赖或实现都可以抽象为输入和输出两类。组织关系变为了一个二维的内外关系,而不是上下结构。每个io与应用程序之前均有适配器完成隔离工作,每个最外围的边都是一个端口。基于六边形架构设计的系统是DDD追求的最终形态。六边形架构的实践在“DDD的优势”部分进行讲解。
先给出基于六边形架构实践后,项目模块结构:

在这里插入图片描述
在这里插入图片描述

DDD 战术设计

战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

实体: 由属性和行为组成,具有唯一标识。
在设计系统时,我们趋向于将重点放在数据上,而不是领域上。对于DDD开发者来说也是如此,因为软件开发中,数据库依然占据着主导地位。首先考虑的是数据的属性和关联关系,而不是富有行为的领域概念。这样做的结果是将数据模型直接反映在对象模型上,导致实体只包含get/set方法,这不是DDD的做法。
只有get/set实体需配合service使用,内聚性、可维护性,以及复用迁移成本均明显高于DDD的做法。

值对象: 没有唯一标识,具有可度量或可描述,并满足不变性的对象。
我们应该尽量使用值对象来建模而不是实体对象,因为相比实体,我们可以非常容易地对值对象进行创建、测试、使用、优化和维护。

领域服务: 领域服务表示一个无状态的操作,它用于实现特定于某个领域的任务。
当某个操作不适合放在聚合和值对象上时,最好的方式便是使用领域服务了。
例如“用户认证”,一种方式是我们可以简单地将认证操作放在实体上。对于这种设计,存在两个问题。首先,用户类需要知道某些认证细节,其次,这种方法也不能显示的表达通用语言。这里我们询问的是一个User“是否被认证了”,而没有表达出“认证”这个过程。在有可能的情况下,我们应该尽量使用建模术语直接地表达出交流语言。

领域事件: 领域专家所关心的发生在领域中的一些事件。我们通常将领域事件用于维护事件的一致性,这样可以消除两阶段提交(全局事务)。

聚合: 聚合是一组相关对象的组合,作为一个整体被外界访问,聚合根是这个聚合的根节点。
聚合是一个非常重要的概念,核心领域往往都是用聚合来表达。其次,聚合在技术上也有非常高的价值,可以指导详细设计。聚合由根实体、实体、值对象组成。

工厂: 工厂提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用实际被创建的对象。

DDD的优势

应用DDD的系统符合六边形架构,我们实现了以下目标:

独立于框架:架构不应该依赖某个外部的库或者框架,不应该被框架的结构所束缚;

独立于UI:前台展示的样式可能会随时发生变化,但是底层架构不应该随之而变化;

独立于底层数据源:软件架构不应该因为不同的底层数据存储而产生巨大改变;

独立于外部依赖:无论外部依赖如何变更、升级,业务的核心逻辑不应随之而大幅变化。

参考资料 https://mp.weixin.qq.com/s/g_kvMHAkqiHyv1O5ZHADKQ
https://juejin.cn/post/7050738599649607694

猜你喜欢

转载自blog.csdn.net/qq798280904/article/details/130647180