绪论
传统数字化转型需要解决的几个问题
1.技术体系落后
可扩展能力不强,高可用能力不强,面对突发高频访问的业务场景,不能实现自动弹性伸缩,发展到一定规模后,数据库性能和容量成为业务发展的瓶颈。
2.单体架构问题
将很多功能放到一个应用中,日积月累,这个应用编程一个庞大的怪物,随着人员的更替,时间一长很少人能完全搞懂这些代码的逻辑关系。一些人担心遗留代码,不敢修改之前的代码,宁愿复制一份代码出来重新修改,导致应用越来越复杂,最终陷入恶性循环。单体也存在模块耦合度高,弹性扩容资源利用不高。
3.研发和运维能力落后的问题
云平台和自动化运维工具对单体应用的生态支撑有限,部署和运维相对复杂,应用出现问题基本靠人肉排查,运维和研发难以快速定位。
4.IT能力重复建设问题
传统企业已经建立了一套单体核心系统,为了在移动互联网开拓业务,又建立了一套移动互联网应用,出现了一些业务同质化问题。为了解决这个问题就需要提升技术能力和重构业务模型入手,实现企业级业务的能力复用。
微服务虽好,但它只是手段不是目的,微服务是为了解决业务和应用扩展能力,也是了更好上云。微服务实施需要一定的前置条件。技术能力提升了,传统应用和移动应用才有统一的基础;应用统一了,才会有业务模型的统一;业务模型统一了,才能实现业务能力共享和复用,企业才会有实施中台战略的技术基础。
为什么会面临单体架构问题?
团队小的时候,单体的开发效率最高
当时团队技术能力不足,研发人员不够重视
服务器经费不足
业务发展不允许,业务上需要快速的实现功能,没有过多的时间去设计系统的扩展性和高可用等。
所以单体架构可能是很多团队不得不面临的一个问题,当企业发展后,单体向微服务的迁移也是一个不得不面临的问题。
认识中台
ThoughtWorks对中台的定义:“中台是企业级能力复用平台”。中台本质上是企业的业务模型。中台如何落地,微服务架构是目前公认的最佳实践,中台落地时会面临微服务应该如何拆分和设计的问题。是否有好的方法来指导微服务的拆分和设计呢?那就是DDD(领域驱动设计),DDD包含战略设计和战术设计两个阶段,通过战略设计完成中台业务边界划分和领域建模,然后将领域模型作为战术设计的输入,完成微服务设计。
阿里对前台、中台、后台职责的定位,前台主要面向客户以及终端销售者,实现营销推广以及交易转换。中台主要面向运营人员,完成运营支撑。后台主要面向后台管理人员,实现流程审核、内部管理、后勤支撑,比如采购、人力、财务、OA等系统。企业级能力往往是前台、中台、后台协作能力的体现,如果把业务中台比作陆军、火箭军、空军等专业军种,主要发挥单一军种的战术专业能力,那么前台就是作战部队,它会根据前线战场的实时作战需求,快速完成不同职能业务中台能力的组合和调度,实现不同业务板块能力的融合,形成强大的组合打击能力完成精确打击,获得最大企业效能。而数据中台就是信息情报中心和联合作战总指挥,是企业智能化的大脑,它能够汇聚各类一线作战板块的数据和信息完成数据分析,制定战略和战术计划,完成不同业务中台能力的智能调度和组合,为前台作战部队提供快速数据和情报服务。后台就是后勤部队,主要提供企业后端支持和管理能力。
中台能力总体框架
DDD包括战略设计和战术设计两部分,它们分别从不同的视角出发,完成领域建模和微服务的拆分和设计。战略设计是从业务视角出发,划分业务的领域边界,建立基于通用语言和业务上下文语义边界的界限上下文,构建领域模型。而界限上下文就可以作为微服务拆分和设计的边界。
战术设计则是从技术视角出发,侧重于对领域模型的技术实现,按照领域模型完成微服务的开发和落地。在战术设计上会有聚合、聚合根、实体、值对象、领域服务、领域时间、应用服务、仓储等领域对象,这些领域对象会以代码的形式映射到微服务中,完成设计和系统落地。
DDD战略设计中的领域建模是一个从发散到收敛的过程,通常采用事件风暴工作法。事件风暴是一项团队活动,领域专家与项目团队通过头脑风暴的形式,罗列出领域中所有的领域事件,然后为每一个事件标注出导致该事件的命令,再为每一个事件标注出命令发起方的角色
基于事件风暴的领域建模的关键过程包括:产品愿景分析(可选)、场景分析、领域建模、微服务拆分与设计这几个重要阶段。原则上,一个界限上下文内的领域模型就可以设计为一个微服务,但领域建模时,只考虑了业务因素,并没有考虑微服务落地的技术、团队、运行环境等非业务因素,因此它只能作为微服务拆分的一个非常重要的依据,而不是唯一依据。构建边界清晰的领域模型,才是我们的最关键目标。
DDD的核心目的是为“高内聚,低耦合”提供一个可行办法。
DDD、中台、微服务的关系
这里有个设计经验,在设计过程中我们可以用一些表格来记录事件风暴和微服务设计过程中产生的领域对象与属性
需要强调一次:DDD分析和设计过程中的每个环节,都需要保证界限上下文内通用语言的统一和正确性。在代码模型设计的时候要建立领域对象和代码对象的一一映射,从而保证业务模型和系统模型一致,实现业务语言和代码语言的统一。
子域和界限上下文的关系?大多数情况下是一对一或一对多的映射关系。有时候业务领域非常庞大,不太方便使用时间风暴对整个领域构建领域模型,所以在领域建模前,我们先根据业务流程边界或功能集合等要素,将庞大领域分解为若干个合适的子域,然后在根据属性划分为核心子域、通用子域、支撑子域,然后再对这些子域内开展事件风暴,划分界限上下文
实体和值对象
在代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务行为和业务逻辑,这些实体通常采用充血模型,跨多个实体的领域逻辑则在领域服务中实现。贫血模型中领域对象大多只有setter和getter方法,业务逻辑统一放在业务逻辑层实现,而不是领域对象中实现。
实体以领域对象(DO)的形式存在,每个实体对象都有唯一的ID,我们可以对一个实体对象多次修改,修改后的实体数据和原来的数据可能会不大相同,但是由于拥有相同的ID,他们仍然是一个实体。比如商品是商品界限上下文的一个实体,通过唯一的商品ID来标示。不管这个商品的数据如何变化,商品的ID一直保持不变,所以它始终是同一个商品。
实体的数据库形态,与传统数据库模型设计优先不同,DDD是先构建领域模型,通过场景分析找出实体对象和行为,再将实体对象映射到数据持久对象。一个实体可以对应0个、1个或多个数据库持久化对象。如何解决一对多或多对一场景?
值对象本质是个属性集合,是不可变的,它没有ID,它只是有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。
聚合与聚合根
实体和值对象都是个体化的业务对象,他们表现出来的是个体的行为和能力。在领域模型中,我们需要一个这样的组织,将这些紧密关联的个体对象聚集在一起,按照组织内统一的业务规则共同完成特定的业务功能,因此就有了聚合的概念。聚合内数据的修改必须由聚合根统一组织,以确保每次数据修改都是按照聚合内统一的业务规则来完成。过去,在传统数据模型中每个实体都是对等的,在业务逻辑实现时,可以随意找到实体或数据库表完成数据修改,但这类操作在DDD的聚合内是不被允许的。
调用关系:
- 聚合内的实体以充血模型实现自身的业务逻辑
- 跨多个实体的业务逻辑通过领域服务实现
- 跨多个聚合的业务逻辑的组合和编排,是通过应用服务来实现的
判断一个实体是否是聚合根?
是否有独立的生命周期
是否有全局唯一ID
是否可以创建或修改其他对象
是否有专门的模块来管理这个实体
怎么理解聚合根管理功能?
聚合设计原则
- 聚合是拆分微服务最小业务单元
- 设计小聚合
- 通过唯一ID引用其他聚合
- 在边界之外使用最终一致性。在聚合内采用数据强一致性,在聚合之间采用数据最终一致性。这时因为DDD强调子一次事务中,最多只能修改一个聚合的数据。如果涉及多个聚合数据修改,那么应采用领域事件驱动机制。怎么理解边界之外?是界限上下文?还是理解成聚合?
- 通过应用层实现跨聚合的服务调用。
聚合根在聚合内对实体和值对象通过对象引用的方式进行组织和协调,聚合与聚合之间只能通过聚合根ID引用的方式,实现聚合之间的访问和协同。
仓储模式就是用来隔离业务实现逻辑与基础层资源实现逻辑,降低它们之间的耦合和相互影响而产生的。
仓库模式包含仓储接口和仓储实现,仓储接口面向领域层提供基础层数据处理相关的访问接口,仓储实现完成仓储接口对应的数据持久化相关的逻辑处理。
领域事件:解耦微服务的关键
到底是将领域事件直接发布到时间总线、消息中间件、或者通过CDC再转发到消息中间件,可根据业务场景选择
因为关键数据不能丢失或者需要数据对账,所以事件发布之前需要持久化到数据库事件表中。
DDD分层架构
微服务目录结构
interfaces目录下面有assembler、dto、facade
assembler实现DTO和DO领域对象之间的相互转换和数据交换,同dto同时出现
dto是前端应用数据传输的载体,前端需要的数据格式或前端传输过来的数据格式。
facade封装应用服务,将用户请求委派到一个或多个应用服务进行处理。
application目录有event、service
event主要是publish和subscribe
service是应用服务,
对于多表关联的复杂查询,由于这种复杂查询不需要有领域逻辑和业务规则约束,因此不建议将这类复杂查询放在领域层的领域模型中,可以通过应用层的应用服务采用传统多表关联的SQL查询方式,也可以采用CQRS读写分离方式完成数据查询操作。
为了实现微服务内聚合的解耦,原则上我们应该尽量避免聚合之间的领域服务直接调用和聚合之间的数据库表关联。聚合之间的服务调用和数据交互,可通过应用服务完成。
domian目录下面的leave、person、rule都是聚合
领域事件订阅建议放到应用层event,领域事件发布相关代码放到领域层和应用层都是可以的
工厂类(factory)放到领域层聚合的service目录下,仓储相关代码放到领域层聚合的repository目录结构下
案例地址:https://github.com/ouchuangxin/leave-sample.git
下图为各领域对象在各层位置和调用关系