系统设计
问题:
为什么你的系统越来越复杂?
为什么代码越来越臃肿?
总是不知道系统该怎么搭建,模块怎么划分,代码写在哪儿?
为什么你做一个功能,就要新加一个接口,接口越来越多?
我们从系统设计的发展过程来看一下,你的系统思路是什么样的:
面向过程
“面向过程”是一种以过程为中心的编程思想。
是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了,部分情况下也有数据的变更,但更侧重有序的步骤。
问题:类似一种瀑布流的编码思路,容易陷入想到哪儿写到哪,每做一个功能就新加一个接口的思路也是一个面向过程的体现
面向对象
“面向对象”是一种以解决问题所需对象模型为核心的编程思想。
是分析解决问题所需要的对象模型,包括对象属性(数据)和对象行为(方法)两大部分,从而基于对象属性实现存储,基于对象行为实现业务逻辑变更(最终体现到数据)
实现上可以分为:面向数据表和面向领域模型
面向数据表
“面向数据表”是一种以数据的存取及布局为核心的编程思想,是一种把业务逻辑转换为数据操作的思想。
是分析解决问题所需要的数据表,然后基于数据表独立<此时属性和行为会被拆分开>构建的一系列的数据操作方法,通过这些方法来实现业务逻辑
问题:容易陷入数据思维,没有清晰的对象结构边界,产生融合非常多数据结构的大对象(会持续增长),无法和其他业务同学统一语言,难以维护,理解
面向领域模型
“面向领域”是一种以“面向对象”为主要思想,同时强调领域边界的领域模型思想,主张属性和行为结合构建对象
Eric Evans的“Domain-Driven Design领域驱动设计”简称DDD,Evans DDD是一套综合软件系统分析和设计的面向对象建模方法
过去系统分析和系统设计都是分离的,系统分析提供需求,系统设计完成编程,因为语言不统一,经常会产生编程结果和最终客户想要的不一致,无论是面向过程,还是面向数据,既无法和业务描述相结合,也无法适应这种快速的需求变更。DDD则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,用领域模型统一业务和技术实现,使得软件能够更灵活快速跟随需求变化
主要思想:
1、统一语言
你确定,你和业务方说的,想的是完全一致的吗?你以为的一致可能往往和下面这幅图一样:
要解决这个问题,首要任务就是要统一语言,不能业务说业务,技术说技术,讲业务模型和实现进行统一,也就是:
a、模型和实现绑定,最初的原型虽然简陋,但只要模型和实现建立了链接,后续所有的迭代一直基于该原型,保证设计和实现不会脱离
b、统一基于模型的语言,不需要不停地作编码和需求的映射,编码方不需要不停地向需求方解释类图,方法,转变为模型属性和行为,业务方不需要不停地讲解业务结构。双方统一使用业务模型语言,达到业务和技术能相互理解
2、分而治之,控制规模
一个好的设计,职责一定是“分治”的,就是让每个高内聚的对象只承担自己擅长处理的部分,而将自己不擅长的职责转移到别的对象,在遇到设计问题时尽量少用集权的机制(不要因为代码量或者为了合而合)。
角色,责任,协作(高内聚,低耦合的具现)
职责由谁来履行?——这牵涉到领域行为该放置在哪一个限界上下文。
结果由谁来保证?——这需要明确领域行为的责任由谁承担。
谁发起对该职责的调用?——倘若发起调用者与职责履行者在不同限界上下文,则意味着二者存在协作关系,且能够帮助我们确定上下游关系。
示例
假设我们要设计一个货物运输系统,需求是:
1、追踪货物位置
2、货物发出和到达能通知客户
1、思考领域模型,而不是去设计数据表和方法(或接口)
那么这个系统业务模型有哪些呢?
从需求层面来看明面能确定的,至少有货物,客户两个参与者,同时客户还分为发送方和接收方两个角色
如果要实现追踪位置,我们还是设计货物的运送历史信息,保含仓库,到达离开的时间
还包含出发和到达两个事件,会触发通知
基本确定业务模型:客户,货物,运送记录,仓库;以及两个事件
(为了示例简单便于理解,暂不考虑快递员和快递公司的场景)
2、分析系统边界,不是所有的模型和能力都属于当前的系统
这个里并没有把通知相关的模型考虑进来,就是因为从运输系统的边界来说,通知更像一个底层业务无关的基础能力,如果当前没有这个能力需要建设,也要和当前系统分割开,保证领域边界的清晰
通知作为该系统的一个依赖服务来构建
3、分析业务能力,构建模型类型,属性,行为
模型一般有两种呈现方式:实体,值对象
具有业务行为,或者在业务或者场景中,模型含义是不同的,一般定义为实体
相反只具备数据承载功能,跟业务,场景无关联,数据保持稳定的,一般定义为值类型
那么,每个客户,每个货物,运送记录,仓库 在不同情况下都是不同的对象,同时具有一些业务行为,所以定义为实体
客户的角色,具有值类型特征,简单情况下可以使用枚举
角色 Role:
值:receiver,sender
客户 Customer:
属性: id, name, mobile, email, role
行为: send, receive
货物 Cargo:
属性:sendLocation, receiveLocation,intro,receiver,sender,transportRecords,sentAt, arrivedAt
行为:leave,arrive
运输历史 TransportRecord:
属性:location,arrivedAt,leftAt
地址 Location:
属性:name,intro,latitude,longitude
仓库 WareHouse extend Location:
属性:cargos
行为:input,check,output
3、分析行为是否属于模型,是否需要构建领域服务进行聚合
判断一个行为是属于模型,还是属于服务的基本要素:
1、行为影响的数据属于谁
2、行为是否依赖其他行为进行组合
3、业务上是否合理
例如:客户的发送和接收快递,明显是不属于自身行为的,需要依赖外部条件,所以构建服务 CustomerService 来实现收发货物
例如:仓库的货物出入仓,检查,就属于仓库自身的行为,只影响自身的货物列表
4、外部依赖建立防腐层
一个外部的接口或者模块的接入,我们需要为其建立一个隔离的适配器层,也称之为防腐层,把接口内容,参数模型和自身系统做一个隔离转换,做到:
1、避免该接入代码的变动或者异常,影响我们主体代码逻辑
2、良好的适配器结构会优化使用一些不好或不易理解的接口的定义
3、相当于领域边界定位的一个明确体现,同时更换接口关注的代码范围和实现方式,会更简单
也就是一个良好的解耦过程,例如:通知适配器
最终设计结构如下:
总结
领域驱动的核心是完成对于领域模型的定义,从而确定业务和应用边界,保证我们的业务模型与代码模型一致性;
领域驱动是一种架构设计的方法论,通过围绕实际业务构建领域模型的方式将复杂的业务领域逐步的拆分,帮最终找出最基础的业务功能与其对应的最基础功能应用的边界;
领域是用来确定功能的范围,范围即是边界,相同的业务问题应该限制在特定的一个功能范围中。一个业务领域可以继续划分,最终实现将业务域进行不断的拆解,从而降低对于整体业务的理解和系统实现的复杂度;
系统设计并不是绝对,选择团队,场景合适的设计方式,统一业务技术的语言即可。