当今微服务盛行之架构师必经之路-领域驱动设计-下
DDD架构
传统分层架构
分层架构设计就是为了帮助我们达到高内聚、低耦合复用性设计和扩展性设计。整洁架构、CQRS、六边形架构等微服务架构都旨在实现“高内聚低耦合”,而分层架构基本原则是每层只能与位于其下方的层发生耦合。分层架构又分为两种:
- 严格分层架构(Strict Layers Architecture),某层只能与其直接下层耦合。
- 松散分层架构(Relaxed Layers Architecture),允许任意上层与任意下层耦合。由于用户接口层和应用服务通常需要与基础设施打交道,许多系统都是该架构。
DDD分层架构
DDD分层架构包含用户接口层、应用层、领域层和基础层;通过这些层次划分,我们可以明确微服务各层的职能,划定各领域对象的边界,确定各领域对象的协作方式。DDD的分层架构中基础层与用户接口层、应用层和领域层都可能有关系,提供基础能力给其他三层调用。
- 用户接口层:显示信息给用户,如对外的model、模型的转换。一般包括用户接口、Web 服务等,只处理用户显示和用户请求,不应包含领域或业务逻辑。用户接口层很重要,在于前后端调用的适配,Facade接口就起很好的作用,包括DO和DTO对象的组装和转换等。
- 应用层:主要包含线程调度,应用服务,与模型进行与实体无关的业务逻辑。理论上不应有业务规则或逻辑,而主要是面向用例和流程相关的操作。
- 应用层位于领域层之上,因为领域层包含多个聚合,所以它可协调 多个聚合服务和领域对象完成服务编排和组合 ,协作完成业务。
- 应用层也是微服务间的交互通道,它可调用其它微服务,完成 微服务间的服务组合和编排 。
- 开发设计时,不要将本该放在领域层的业务逻辑放到应用层。因为庞大的应用层会使领域模型失焦,时间一长微服务就会演化为传统MVC三层架构,导致业务逻辑混乱。
- 应用服务是在应用层,负责服务的组合、编排、转发、转换和传递,处理业务用例的执行顺序以及结果的拼装,以粗粒度服务通过API网关发布到前端。还可进行安全认证、权限校验、事务控制、发送或订阅领域事件等。
- 领域层:业务概念、规则、领域模型。主要包含聚合、聚合根、实体、值对象、领域服务等领域模型中的领域对象。
- 聚合根:如果把聚合比作组织,聚合根则是组织的负责人,聚合根也叫做根实体,它不仅仅是实体,还是实体的管理者。
- 聚合:高内聚低耦合,是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但是不建议单独对应一个微服务,除非是对性能有极致要求的场景,一个微服务可以包含多个聚合,聚合之间的边界是逻辑最天然的边界,有了这个逻辑边界,就可以在微服务拆分的时候作为拆分和组合依据,微服务架构演进也就不是难事了。
- 聚合根的特点:聚合根是实体,具备唯一标识,有独立的生命周期,一个聚合只有一个聚合根,聚合根在聚合之内采用引用依赖的方式对实体和值对象进行组织和协调,聚合根和聚合根之间通过唯一id进行聚合之间的协同;
- 实体的特点:具备id标识,可以通过id进行相等性比较,实体在聚合内唯一,但是状态可变,它依附于聚合根,它的生命周期由聚合根管理,实体一般都会持久化,跟数据持久化对象存在多种对应关系(一对一,一对多,多对一,1对0),实体可以引用聚合中的聚合根,实体,值对象;
- 值对象特点:无id,不可变,无生命周期,用完即失效,值对象之间通过属性值判断相等性,他的核心是值,是一组概念完整的属性集合,用于描述实体的特征和状态,值对象尽量只引用值对象;
- 聚合设计步骤
- 通过事件风暴(用例分析,场景分析,用户旅程分析)得到实体和值对象,然后找出聚合根,按照高内聚低耦合的设计原则,找出跟聚合根紧密关联的实体和值对象,即形成聚合,并画出聚合内的实体和值对象的引用依赖关系,最后把业务把关联紧密的聚合画在同一个限界上线文中,即完成了领域建模。
- 聚合设计原则
- 聚合的设计原则: 高内聚,聚合尽量小,聚合之间通过id关联,边界之外使用最终一致性,在应用层实现跨聚合的调用。
- 实现核心业务逻辑,通过各种校验保证业务正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。
- 领域模型的业务逻辑主要由实体和领域服务实现:实体采用充血模型 实现所有与之相关的业务功能。
- 实体和领域服务在实现业务逻辑上不是同级,当领域中的某些功能,单一实体或值对象无法实现,就会用到领域服务,它可组合聚合内的多个实体或值对象,实现复杂业务逻辑。
- 领域服务:
- 基础层:是一个交互层次,为其它各层提供通用的技术基础服务,包含三方工具、驱动、MQ、API网关、文件、缓存、DB、基础服务等;最常用的还是提供DB持久化。
- 基础层包含基础服务,它采用依赖反转,封装基础资源服务,实现应用层、领域层与基础层解耦。
- 传统架构由于上层应用对DB强耦合,在架构演进最怕换DB,一旦更换带来工作量较大。但采用依赖反转,应用层即可通过解耦保持独立核心业务逻辑。当DB变更,只需更换DB基础服务。
- 防腐层:用于应对业务的变化,形成抽象业务,例如抽象MQ基础设施层防止第三方组件的变化比如从Kafka更换为Pulsar
- 仓库:为了解耦领域逻辑和数据处理逻辑,在中间加了薄薄的一层仓储。仓储模式包含仓储接口和仓储实现,仓储接口面向领域层提供基础层数据处理相关的接口,仓储实现则完成仓储接口对应的数据持久化相关的逻辑处理。一个聚合配备一个仓储,由仓储完成聚合数据的持久化。领域层逻辑面向仓储接口编程,聚合内的数据持久化过程为DO(领域对象)转PO(持久化对象)。当需要更换数据库类型,或者更改数据处理逻辑时,我们就可以保持业务逻辑接口不动,只修改仓储实现,保证了领域层业务逻辑的干净和纯洁。后面基础层发生了变化,则领域层无需动任何代码,只要仓储接口不变,领域层的逻辑就可以一直保持不变,维护了领域层的稳定性。领域服务是可以做成企业级可复用的服务的,因此稳定性必须有保障。
- 工厂:DO对象创建时,需要确保聚合根和它依赖的对象同时被创建,如果这项工作交给聚合根来实现,则聚合根的构造函数将变得异常庞大,所以我们把通用的初始化DO的逻辑,放到工厂中去实现,通过工厂模式封装聚合内复杂对象的创建过程,完成聚合根,实体和值对象的创建。DO对象创建时,通过仓储从数据库中获取PO对象,通过工厂完成PO到DO的转换,工厂中还可以包含DO到PO对象的转换过程,方便完成数据的持久化。
DDD四层架构规范
- 领域中的对象由实体和值对象组成;对值对象的访问必须经由其所属的实体对象。
- 相关联的一组实体和值对象组成聚合;对聚合内的对象的访问必须经由聚合根对象。
- 跨实体的操作必须经由领域服务。
- 应用服务层只通过领域服务或聚合根来组织业务,自身不带任何实现逻辑。
- 业务和数据隔离,领域层只关注业务,数据支撑全部交由基础设施层。
DDD架构和MVC架构
- MVC架构,目前典型实现包括SpringMVC,Spring Boot,固化业务,是一种结构性设计模式,也是一种面向数据的设计。
- 行为型设计模式:DCI架构,面向对象编程新构想,对于MVC模式的补充,面向对象量化的落地。包括Data(对象数据),Context(对象场景)、InterActions(交互行为)。DCI架构模式是架构层面的方法论,而四色建模法则是需求层面的方法论。
- 从DDD的角度看MVC架构的问题
- 代码角度
- 瘦实体模型:只起到数据类的作用,业务逻辑散落到service,可维护性越来越差;
- 面向数据库表编程,而非模型编程;
- 实体类之间的关系是复杂的网状结构,成为大泥球,牵一发而动全身,导致不敢轻易改代码;
- service类承接的所有的业务逻辑,越来越臃肿,很容易出现几千行的service类;
- 对外接口直接暴露实体模型,导致不必要开放,内部逻辑对外暴露,就算有DTO类一般也是实体类的直接copy;
- 外部依赖层直接从service层调用,字段转换、异常处理大量充斥在service方法中;
- 控制器冗余,控制器对于对象依赖过重,对象之间会产生耦合。由于Spring的存在,其实我们的开发是不符合面向对象的。
- 项目管理角度:
- 交付效率:越来越低;
- 稳定性差:不好测试,代码改动的影响范围不好预估;
- 理解成本高:新成员介入成本高,长期会导致模块只有一个人最熟悉,离职成本很大;
- 代码角度
- MVC架构到DDD分层架构的映射
- DDD优点
- 业务逻辑清晰、业务人员也可以读。
- 业务稳定度,业务不动,代码不动。
- 防腐层隔离变化。
- 各领域内自治,可以自我发展。
- 用仓库来管理对象的存储,仓库中集成工厂Factoty/Builder应对复杂对象的组装。
- DDD优点
DDD项目包结构
顶级包目录下DDD四层目录如下:
总结
-
微服务的拆分第一个层面就是数据库层面的拆分,第二层面就是上层应用功能业务层面的拆分。但如果系统上层逻辑是依赖底层一个大的数据资源,那么微服务拆分不当就有可能导致拆分后的微服务出现大量的跨库查询、分布式事务的情况。基于DDD的思路去做微服务拆分,包括进行事件风暴。
- 把相关的核心业务事件全部梳理出来,找到关键的对象基于对象做聚合,做完之后就可以梳理出一个关键内容叫限界上下文,而限界上下文是微服务拆分一个很关键的点,但这也可能导致后续微服务拆分过细带来相关问题。命令、操作、对象、对象状态。
-
DDD切入点:理解领域、拆分领域、细化领域
- 理解领域知识是基础。这里要强调领域专家的重要性。因为领域专家对领域内的各种业务场景和各种业务规则非常清楚,哪些是核心业务关注点,靠的是沉淀领域内的各种知识。
- 拆分领域:领域建模的基础是要先理解领域,让自己成为领域专家。有时一个领域往往太复杂,涉及到的领域概念、业务规则、交互流程太多,需要将领域进行拆分,本质上就是把大问题拆分为小问题,然后各个击破的思路。然后既然把一个大的领域划分为了多个小的领域(子域),那最关键的就是要理清每个子域的边界;然后要搞清楚哪些子域是核心子域,哪些是非核心子域,哪些是公共支撑子域;然后还要思考子域之间的联系是什么。
- 细化子域
- 梳理领域概念:梳理出领域内我们关注的概念、概念的关系,并统一交流词汇,形成统一语言;
- 梳理业务规则:梳理出领域内我们关注的各种业务规则,DDD中叫不变性(invariants),比如唯一性规则,余额不能小于零等;
- 梳理业务场景:梳理出领域内的核心业务场景,比如电商平台中的加入购物车、提交订单、发起付款等核心业务场景;
- 梳理业务流程:梳理出领域内的关键业务流程,比如订单处理流程,退款流程等;
-
领域就是问题域,有边界,领域中有很多问题;
-
任何一个系统要解决的那个大问题都对应一个领域;
-
通过建立领域模型来解决领域中的核心问题,模型驱动的思想;
-
领域建模的目标针对我们在领域中所关心的问题,即只针对核心关注点,而不是整个领域中的所有问题;
-
领域模型在设计时应考虑一定的抽象性、通用性,以及复用价值;
-
通过领域模型驱动代码的实现,确保代码让领域模型落地,代码最终能解决问题;
-
领域模型是系统的核心,是领域内的业务的直接沉淀,具有非常大的业务价值;
-
技术架构设计或数据存储等是在领域模型的外围,帮助领域模型进行落地;
-
DDD架构作为一套先进的方法论,在很多场景能发挥很大价值,高级的架构师把DDD架构当成一种工具,结合其他架构经验一起为业务服务,但是DDD也存在一些不足
- 性能:DDD是基于聚合来组织代码,对于高性能场景下,加载聚合中大量的无用字段会严重影响性能,比如报表场景中,直接写SQL会更简单直接;
- 事务:DDD中的事务被限定在限界上下文中,跨多个限界上下文的场景需要开发者额外考虑分布式事务问题;
- 难度系数高,推广成本大:DDD项目需要领域专家专家,且需要特别熟悉业务、建模、OOP,对于管理者来说评估一个人是否真的能胜任也是一件困难的事情;
**本人博客网站 **IT小神 www.itxiaoshen.com