中台与DDD
概念
什么是中台
- 按照可复用原则,将通用的、可复用的能力沉淀到中台,完成企业级业务模型重构
- 中台落地的技术实现有很多种,当前微服务架构是公认的最佳实践。
- 中台本质是业务领域的子域
微服务与DDD是共生关系
- 微服务提倡将应用进行服务化拆分,通过业务领域边界实现服务边界的划分。
- 而微服务的拆分困境在于不知道业务或者应用的边界在什么地方
- DDD恰好提供一种基于业务界限上下文边界来划分业务的方法
- DDD是一种设计方法,微服务是一种架构风格,两者都是为了追求软件的高响应力,从业务视角去分离应用系统复杂性的手段
- DDD关注点在于业务抽象、领域建模,维持业务和代码逻辑的一致性。微服务的关注点在于实现去中心化,架构演进。
- 当然,微服务还可以考虑性能、伸缩等技术要求,单独将一块业务领域剥离到一个服务,也是可以的,这块最小可以做到把DDD中的一个聚合单独剥离成一个微服务
- 中台和微服务设计的关键,在于领域模型设计(横向解耦),以及微服务的合理分层设计(纵向解耦)
DDD
战略设计
-
概念
-
子域
- 子域是可以不断递归划分的,关键还是要看你的业务理解,以及团队的大小
- 在很多场景中,很多流程节点框定的边界天然就是子域
- 子域是从问题的视角出发,将大问题分解成小问题,所以可以直接从感觉上划分
-
界限上下文
- 界线上下文是从解决方案的视角触发。所以一般是子域划分好了,然后在里面罗列领域对象,如果这些领域对象还可以再分块内聚,则在子域内通过界限上下文将它们隔开
- 界限上下文本质上就是子域,只是它会更加考虑技术实现和领域对象边界这些细节,不像划分子域时只需要明确问题就行。
- 子域和界限上下文的关系是一对一或者一对多的关系
- 界限上下文内的部分叫做领域模型
- 界限上下文就是业务的实际边界,通常可以作为微服务的边界。当然,考虑团队和架构等非业务因素,也可以把更低级别的聚合或者更高级别的子域作为微服务边界
-
实体
-
有唯一标识符
-
重要的不是其属性,而是其标识符和延续性
-
可以修改部分值
- 需要通过聚合修改,而不是直接在数据库获取数据记录然后直接修改
-
一般是实实在在看的着的业务对象,有业务属性、业务行为、业务逻辑
-
-
值对象
- 没有唯一标识符
- 通过属性来标识,本质就是一个属性的集合
- 不能修改内部值,只能整体替换
- 只是作为一个修饰的属性集合,初始化完了就再也不变,不包含业务逻辑
- 还可以将实体是数据作为值对象存储,起到快照的作用
-
聚合
-
聚合之间也是有上下文边界的,只是这个边界比界限上下文的边界小
-
聚合之间是松耦合的,需要避免互相调用,可以通过在应用服务层实现互相调用。
-
如何判断是否是聚合根
- 是否有独立的生命周期
- 是否有全局唯一ID
- 是否可以修改其他对象
-
我们一般在一个聚合之内使用事务保证强一致性,在聚合之间只需要保证最终一致性,用以达成松耦合
-
-
仓储模式
-
值对象在序列化时,可能为了简化,存在实体表里,有两种存储形式
- 直接序列化成json存一个字段
- 没个字段也作为单独的字段存储在实体表里
-
仓储模式主要完成领域对象的持久化,用来隔离数据库实现,类似DbService,其中DbService接口属于领域层,DbServiceImpl属于基础层
-
仓储服务使用的是PO数据,而由DO转PO是由工厂模式实现。
-
-
工厂模式
- 工厂模式主要完成领域对象的创建和初始化,如果领域对象的初始化简单的话,也可以放在聚合根里完成
- DO和PO互转的工作,也是由工厂完成,因为这也属于创建对象的一部分。
- 引入工厂的核心原因就是,让聚合能够聚焦业务逻辑,将创建对象这种通用能力单独剥离
-
领域事件
- 如果某个事件的发生,会进一步触发业务动作,那这个事件就很可能是领域事件
- 如果事件不允许丢失,则事件在发送之前,也需要持久化到数据库中。这也就是我们常用的本地事件表模式。
-
-
流程
-
明确主题、 产品愿景
-
领域建模(横向)
-
方法
-
事件风暴
-
步骤
-
根据所有角色的使用场景,确定所有的流程
-
罗列出领域中的所有事件,标注出导致该事件的命令
-
对事件进行分类,整理出实体、聚合、聚合根以及界限上下文
- 需要考虑依赖关系,截断循环依赖
-
-
关键点
-
业务的通用语言中,一般名词就是领域对象,动词就是命令或者领域事件
-
事件风暴的本质是由底向上归纳,既先摊开所有细节,再一层一层归纳总结,是一个从发散到收敛的过程
-
有时在迭代过程中新增的领域逻辑,可以单独作为一个副领域单独拆出来,而不一定要将原先的领域模型改的更通用来进行适配,可以等副领域的逻辑稳定后,再进行重构
- 这就相当于利用动静分离的原则,而不是单独的DDD
-
-
-
-
交付件
- 完成后,所有横向的子域、聚合都得到了划分,甚至还有实体的关键参数等等
-
-
战术设计
-
服务识别和设计
-
方法
-
将命令作为服务设计的起点,然后按分层架构进行每层的内容设计,既在横向划分好的结构中补充纵向内容
-
横向的代码耦合,不用开方法实现,直接看方法定义以及类的字段就能看到,因为代码实现里的调用,肯定是靠注入的服务或者入参来获取引用,这些地方不能发生耦合。
-
纵向的分层不清,需要根据具体实现来判断,因为像实体会在所有层次作为参数传递,但是在应用层最好不要调用多个实体的方法,这个只能通过查看方法实现,光看定义看不出来
-
实际代码设计时,有的时候很难做到分层那么清晰,而且会引入过多的封装,此时要注意权衡,核心的原则还是在保证数据一致性等功能特性的情况下,做到高内聚低耦合
- 一个简单的判断依据,就是这个代码是不是在任何地方看起来都很好演进,比如流程的逻辑不会出现在领域层。
-
-
-
交付件
- 在按命令进行服务设计时,会得到各层服务,以及完善聚合内的方法。
-
-
分层架构(纵向)
-
结构
-
用户接口层
-
主要完成前后端分离,进行前段数据和接口的适配
-
主要模块
- facade
- assembler
- dto
-
-
应用层
-
主要职责是协调多个聚合间的编排和组合
-
微服务间的调用只能在应用层
-
易变的如流程、业务组合、编排的业务需求放在应用层,但是不要将领域逻辑放在应用层,会导致应用层失焦
-
主要模块
-
应用服务
- 服务、聚合的组合编排
- 权限、事务、安全等非业务逻辑
- 结果拼装
- 应用层因为跨微服务调用,也有可能会与dto和assembler模块
-
事件发布和订阅
-
-
-
领域层
-
主要实现领域模型的核心业务逻辑
- 此处的业务逻辑主要是指聚合自身的原子业务逻辑,不是用户操作或者流程等方面的业务逻辑
-
一般是不变的核心逻辑,才下沉到领域层,要区分好易变的业务逻辑,和不变的核心逻辑。
-
一般涉及一个实体的业务逻辑,在实体内部实现,涉及多个实体的,在聚合根或者领域服务中实现
-
主要模块
- 工厂
- 聚合根以及其他实体
- 领域服务
-
-
基础层
-
主要提供通用技术和基础服务
-
基础层在传统4层架构里是被所有层依赖,在新4层架构里,是依赖所有层,本质就是多加了一层接口做依赖倒置而已,没有逻辑上的改变
-
主要模块
- 仓储服务
- 缓存、网关等
-
-
-
关系
-
严格分层和松散分层的区别在于,严格分层中,任何层只能与直接依赖层发生依赖
- 遵循严格分层,既是清晰层间的职责,更是方便以后一个微服务拆分成多个微服务时,聚合可以方便的拆分成微服务
- 在查询类的接口中,允许应用层直接调用基础层,通过sql实现高性能,因为此时也没有太多业务逻辑需要下沉。
-
当任何层次出现重发代码时,就要考虑重组并下沉能力
-
有时候两层的功能一定要放在一层处理时,关键是要考虑其主要能力是否是需要下层。比如装配数据时还校验数据,此时放在应用层会比放在用户接口层好一点,因为需要把校验能力沉淀下来。
-
-