讲代码重构前,必须讲到软件架构设计,初期没有好的设计,再厉害的程序员,在堆积如山的屎山代码面前,都没有办法把原本一团乱麻的逻辑,层层剖析开,建立起新的代码金字塔
代码重构设计
代码编排
1、分解阶段和步骤: 垂直切分+水平扩展
想象一下,一个复杂的、具有类似功能的、具有个性功能的多表业务,如何去编排他的代码结构?
或者一个下单业务场景,要先检查商品数量、检查收货范围、检查用户状态,然后开始组装下单数据、订单数据、物流数据等,最后要进行调用支付渠道,这样一个复杂的流程,如何去编排他的业务代码?是全部写在一个service中吗?显然不可能!
1、把业务场景分割,按照操作性质,分成多个阶段
1、把阶段里的实现按照简单的操作步骤,抽象出多个表之间共性的操作
2、将这些共性操作划分成具体的步骤,封装成一个抽象类,在业务层面进行简单的业务步骤垂直切分
public abstract class AbstractBaseHandler extends AbstractHandler<ExcelModelContext, Void> {
private Logger log = LoggerFactory.getLogger(AbstractBaseHandler.class);
@Override
public Void execute(ModelContext context) {
commonStep(context);//共有步骤
step1(context);//第1步
step2(context);//第2步
}
public void commonStep(ModelContext context){}{
}
//抽象方法
public abstract step1(ModelContext context);
//抽象方法
public abstract step2(ModelContext context);
}
3、基于这个抽象类,将多表的业务操作继承该类,形成软件结构上的独立业务处理模块,可以参考DDD领域驱动设计思想
4、但是一个业务处理模块里,写上全部的代码,仍然会非常庞大,往往业务处理模块需要再细分模块,使用设计模式的聚合等模式
上面这种简单形式,基本可以适用所有的应用场景,是一个通用的方法,但代码最多只能做到不难看,绝对算不上优美,只是把原先大坨大坨的、甚至上千行的业务代码,变成一块块的独立的业务代码,还是堆叠在一起
此种方法就是基于对过程的分解,将一个业务变为多个阶段,再在阶段中细化操作步骤,实际上还是过程化的编排方式,只是提高了代码可读性,但是没有提高抽象性和复用性
2、过程分解 + 对象模型
上述的过程分解的表达方式,还是一种简单朴素的模式,但这种代码编排,虽然可以实现快速的业务分析与代码编排,能比较好的进行代码维护,但缺少了从整个软件系统结构视角上的立体化设计,业务的可复用程度不高,只能针对某个场景进行单一的快速代码编排
这种过程化拆解,没有聚合领域知识,逻辑没有沉淀,只有单一的使用场景,我们缺失了模型和模型之间的关系,脱离模型的业务表达,是无法显性的表达我们的业务的
上述文中举例的下单业务,校验阶段中需要校验普通商品库存大于1,组合商品库存量大于2,按照只有过程化代码编排,需要先判断该商品属于单品还是组合商品,然后check不同类型的商品数量,这种具有很多if/else的流程化代码,显然不能完成商品领域内的业务沉淀,下一次碰到其他业务场景,比如秒杀等同样判定商品库存量的,则还要写一遍
商品的继承关系如下图
领域化改造的分析过程
写复杂业务的方法论
自上而下的过程分解 + 自下而上的对象建模
过程分析可以理清模型之间的关系,对象模型可以提升代码的复用度和业务语义表达能力
面向对象的DDD领域建模,就是将业务能力下沉到底层
但是并不是所有的业务都要下沉到domian层,这将会并不需要、复用度不高的业务场景把domain搞得很膨胀,并不利于domain的维护,那些不需要复用的,暂时放在Use Case中即可(Use Case即一个request的处理过程,在代码整洁之道中,指的就是独立的、单一的单个业务)
循序渐进的能力下沉策略,应该是更符合实际、更敏捷的方法,因为模型不是一次设计出来,是迭代演化而来的
多个App层的能力下沉
DDD业务结构设计
模型建立过程
STEP1 业务流程分析
首先需要对模型进行抽取,需要分析业务场景下的主过程式流程, 即需求分析
STEP2 用例设计,模拟现实世界
找出了业务流程中操作的对象实体,如上述业务流程,分为美团、厂家、商家等
STEP3 领域分裂,拆解出核心领域
1、分析出每个流程域涉及到的表单,如采购流程涉及到采购单,领用设备流程涉及到出库入库单
2、针对上述各流程域进行分解,根据业务复杂度,按照自底向上/自顶向下的方式进行拆解/合并,多个子域拆解进大的领域,一个复杂领域拆解成多个简单子领域
3、最终根据分析出的业务流程,回归领域设计,是否可以满足该业务流的发展,完善领域设计
领域建模是将一个系统划分成了多个子域,每个子域都是一个独立的业务场景,每个子域的实现就是“限界上下文",他们之间的关系是“ 上下文地图"
STEP3 数据模型分析与建立
按照领域模型,设计各个微服务的数据库
STEP1建立流程与模型初始概念,STEP2将过程化业务流程变为面向业务领域的业务建模,
那么到了STEP3,则需要将模型真正放到业务里来,进行模型化的业务流转
DDD的设计与开发流程
需求分析
使用统一语言建模进行业务沟通 : ddd是基于现实业务的真实建模思想,要参与真实业务的了解与分析,使用专业领域的语言与客户进行沟通,让开发人员与客户的业务专家进行头脑风暴
1、针对每一个领域进行业务分析,增加各种事件
2、识别模型中可能涉及的聚合以及聚合根
微服务拆分
拆分原则: 单一原则,小而专,微服务内高内聚,微服务间低耦合
通过DDD进行业务建模,在基于领域模型进行限界上下文划分,能保证系统的设计,在限界上下文内高内聚,在限界上下文之间低耦合
总结
DDD最佳实践方式: 具体
聚合
整体与部分的关系, 整体封装部分的操作,要识别聚合关系
仓库&工厂
发生聚合后,订单与订单明细的增删改,通常会用一个仓库去实现,封装到一个仓库层中实现
增删改 : 通过订单仓库的封装,只需要在领域对象建模的时候,设定对象间的关系,即将其设定为“聚合”
查询 : 订单仓库查询到该订单后,将其封装在订单对象中,通过查询填补用户对象和订单明细对象,需要用工厂进行数据装配
DDD里的工厂与设计模式的工厂是两回事
带缓存层的仓库 : 即仓库层将dao、nosql等屏蔽起来,数据访问与业务层完全割裂
通过聚合实现了整体与部分的关系
对缓存、对数据库的操作都封装在了仓库与工厂中
数据的查询——通过工厂进行填补与装配
限界上下文
限界上下文: 通过上下文地图相互调用时,通过接口进行调用
比如电商常见的下单功能,可以划分为几个领域: 购物 - 下单 - 支付 - 物流 ,
下单模块A,依赖支付模块B,如果直接依赖,则其他使用A模块的领域,则也要使用BCD,成本太高
但若模块A依赖B‘接口,所有调用模块A的地方,不一定就需要B,B’接口有B和F两种实现,即支付功能可以增加微信支付、支付宝支付、银行卡支付等多种方式,形成低耦合,高内聚
可以通过微服务来实现"限界上下文"之间的低耦合
DDD往往应用于系统增删改的业务场景中,查询场景不用DDD
领域事件通知
如订单系统,将系统划分成用户下单、饭店接单、骑士接单等领域,当用户在下单domain中进行下单,形成一个订单,产生一个事件,此时饭店需要接单,另外一个微服务需要及时获得通知
——使用消息队列,解耦
使用消息队列实现领域事件在微服务间的通知
综合应用
下图是下单、接单、派送3个领域建模的增删改、查询和消息队列的场景应用
DDD代码分层架构
四层模型
五层模型
六边形模型
体系结构设计
架构师能力标准
1、将业务转换为技术,业务需求可以落地到技术方案
2、合理利用技术支撑业务
系统技术架构图
系统业务架构图
微服务开发过程问题
当发生需求变更时,是局部服务进行修改,也会涉及到微服务之间的服务调用,当一个微服务团队向另一个微服务团队提出接口调用需求时,另一个微服务团队该如何设计呢?
当多个团队都在向你提出API接口时,如何提供接口?
对这些接口进行规划,通过复用,用尽可能少的接口满足他们的要求
当调用方需要接口变更时怎么办?
变更现有接口应当尽可能向前兼容,即接口的名称与参数都不变,只在内部增加新的功能
调用方如何调用接口?
异步调用/同步调用
防腐层
比如用户下单领域中包含用户注册,当系统不断完善设计和扩展,用户注册需要单独拎出来到用户领域内,则原先依赖用户下单、取消订单等依赖注册的逻辑都会报错,维护成本太高
可以在下单领域内,放置一个feign,其他的service都不需要修改了,维护成本变低
防腐层 : 接口变更时,降低维护成本
数据管理
仓库层屏蔽了数据操作,在仓库层以下,要根据业务的数据特性,选用不同的数据库
1、对于数据量稳定但需要频繁查询的用户表、饭店表,使用redis作为缓存层
2、3、对于有并发场景数据量巨大的订单下单,使用关系型数据库
对于报表分析、订单查询,从生产上同步数据到es等平台
由于数据库的拆分,不能随意join了,要使用工厂进行数据填充装配,干掉join操作,使用分页后的数据填充,为了提升海量数据的查询性能,可以适当增加冗余字段
当系统要在某些查询模块进行订单查询时,可能对各个字段都需要进行过滤查询,利用NoSQL的特性,采用“宽表”的设计
整洁架构设计
DDD下沉 : 技术中台
传统的领域驱动设计,是每个模块驱动和设计各自的仓库与工厂,这样大大增加开发与工作量,但其实大致都是相同的,会产生大量的重复代码,
是否可以通过抽象,提取出共性,形成通用的仓库与工厂,下沉到底层技术中台中?
从而降低领域驱动的开发成本与技术门槛 —— 实现DDD领域驱动设计,还需要相应的平台架构支持
每个DDD模块都需要微服务间通过feign接口相互调用,数据要通过补填关联查询,还有聚合的实现、仓库和工厂的设计,如果每个模块都要反复的实现这些功能,DDD的设计将异常繁琐
——需要一个既支持DDD、又支持微服务的技术中台
技术中台应该能够封装那些繁琐的聚合操作、仓库与工厂的设计,以及相关的各种技术,将精力放在业务开发上面,降本增效,技术门槛降低,技术更迭方便,上手容易,不再受限于技术细节
底层技术的更迭
过去: 架构是软件系统中最稳定不变的部分
现在: 好的架构源于不停的演变
设计思考: 如何让底层的架构更易于技术更迭,易于架构调整, 来应对不断演变的新技术、新框架
老系统技术架构升级成本极高的原因,是业务代码与底层技术框架太过于耦合,解决思路就是对他们进行接耦 ——在上层业务代码与底层技术框架之间建立接口层
如上图,中间接口层编写实现类去调用底层框架,实现真正的持久化
整洁框架核心设计思路
如何轻松实现架构更迭演化,又能保证开发团队的快速交付?
业务代码与技术框架解耦,可以实现架构快速更迭&快速交付
整洁框架核心设计思路 :
业务领域层设计的实质,就是将领域模型,通过贫血模型或者充血模型的设计,最终落实到对代码的设计,在此基础上,将业务领域层与其他各个层次进行解耦
各中台对比
系统分为前台与后台,提取出的公用组件,属于前台还是后台呢?
其实,公用的组件既包含前台的界面,也包含后台的逻辑,所以被称为中台
中台: 将以往业务系统中可以复用的前台与后台代码剥离个性、提取共性,形成的公用组件
因此,阿里提出小前台,大中台的战略
业务中台: 将抽象的业务组件,做成微服务,各个业务系统都可以使用,如会员管理、权限管理、仓储管理等
技术中台: 封装各个业务系统所要采用的技术框架,设计出统一的API,使上层的开发门槛降低,提升交付速度
数据中台: 整理各个业务系统的数据,建立数据存储与运算的平台
DDD模式下的快速交付团队
如果业务变得越来越复杂、参与的人越来越多,交付速度就会越来越慢,团队不能适应快速变化,
软件规模化发展是软件的必然趋势,要解决规模化团队与软件快速交付的矛盾
大前端+技术中台,可以提高规模化团队的交付速度,但是组建这样一个团队的成本非常高,既要懂技术,也要懂业务,既要懂UI,也要懂前端,每个人必须是全栈工程师
这时就需要架构团队,这里的架构就不是一个人了,而是一个团队,
架构团队通过技术选型,构建技术中台,将软件开发中诸如UI、应用、数据库甚至大数据等诸多技术进行了封装,然后以API接口的形式开放给上层技术
架构团队从业务开发的角度进行提炼,提炼共性、保留个性,将这些共性沉淀到技术中台中,通过将DDD与微服务涉及的各个技术组件封装到技术中台中,封装各个技术细节
箴言
宁愿花更多的时间去分析设计,让软件设计精简到极致,从而花更少的时间去编码