天天看点

领域驱动设计详解:是什么、为什么、怎么做?

领域驱动设计详解:是什么、为什么、怎么做?

一 什么是领域驱动设计

领域驱动设计的概念是2004年Evic Evans在他的著作《Domain-Driven Design : Tackling Complexity in the Heart of Software》(中文译名:领域驱动设计:软件核心复杂性应对之道)中提出的,从领域驱动设计提出距今已经有15年的时间,为什么最近才开始在中国的互联网圈大行其道?似乎一夜之间大家都在谈论,那么领域驱动设计到底帮我们解决了什么问题?带着这些疑问,一起来看下阿里巴巴文娱是如何实践领域驱动设计的。

二 领域驱动设计大行其道的必然原因

软件系统从来都不是凭空而来,而是以软件的形式解决特定的问题。当我们面临现实世界的复杂问题时,如何以软件的形式落地?领域驱动设计是一套方法论,指导我们将复杂问题进行拆分、拆分出各个子系统间的关联以及是如何运转的,帮助我们解决大型的复杂系统在落地中遇到的问题。

Evic Evans在著作中将软件系统的设计分为2个部分:战略设计和战术设计。在战略设计层面提出了域、子域、限界上下文等重要概念;在战术设计层面提出了实体、值对象、领域服务、领域事件、聚合、工厂、资源库等重要概念。如图1所示:

领域驱动设计详解:是什么、为什么、怎么做?

战略设计部分指导我们如何拆分一个复杂的系统,战术部分指导我们对于拆分出来的单个子系统如何进行落地,在落地过程中应该遵循哪些原则。

以大家熟知的电子商务系统举例,早期的电商系统因为业务相对简单,用户量和团队规模也较小,一个单体应用就可以搞定,随着容量上升可以将单体应用进行横向扩容,比如早期的淘宝就是这样做的。拆分过程中我们可以把电商系统这个单体应用拆分成订单子系统、库存子系统、物流子系统、搜索推荐子系统等等,如图2所示:

领域驱动设计详解:是什么、为什么、怎么做?

领域驱动设计在战略层面上的域、子域、限界上下文的划分思想和微服务的划分不谋而合。域对应一个问题空间,也就是上例中的电商系统;子域是把域这个大的问题空间拆分成若干个小的更容易解决的问题空间,也就是单体应用向微服务演进过程中划分出来的各个子系统;限界上下文是解决方案空间,每个子域对应一个或多个解决方案空间。微服务的划分是也是将一个大的问题拆分成若干个小的问题,每一个小的问题用一个或多个微服务来解决。

对于大多数开发同学来说都没有机会接触系统的划分,这些工作一般是公司的技术领导层与架构师来做的,普通的开发同学日常工作中接触到的只是某一个具体微服务或微服务中某一个模块的落地,那是不是说领域驱动设计对于普通开发同学来说就没有用了?当然不是这样,领域驱动设计中的战术设计部分就是指导我们如何落地一个系统才可以使系统具备高可扩展性、高可读性。

所有的系统最终都要以代码的形式落地,而落地的工作都是由普通的开发同学来做的,系统是否具备高可扩展性、高可读性直接影响了整个团队的效率。

三 传统分层架构存在的问题

对于大多数开发同学来说,大部分时间都花在落地一个个微服务上,下面我们来看阿里文娱是如何结合领域驱动设计的思想将微服务进行战术落地的。

目前笔者接触过的微服务大多数都是分层架构并且在Service层与Manager层实现具体的业务逻辑,使用DO、DTO、BO、VO等进行数据传输,数据和行为基本完全隔离。这种分层结构图3是《Java开发手册》中的标准分层结构。

该规范中定义了各层的职责,其中最重要的两层Service层和Manager层是这样规范的(以下两层解释摘抄自《Java 开发手册》):

Service层

相对具体的业务逻辑服务层。

Manager层

通用业务处理层,它有如下特征:

  • 对第三方平台封装的层,预处理返回结果及转化异常信息。
  • 对Service层通用能力的下沉,如缓存方案、中间件通用处理。
  • 与DAO层交互,对多个DAO的组合复用。
领域驱动设计详解:是什么、为什么、怎么做?

阿里文娱早期的项目分层也基本都采用这种架构形式。上面的分层并没有问题,但是这种分层架构采用的是包的形式进行的层与层的隔离,需要每一位开发同学理解并且自觉遵守以上规范,但是在实际工作中我们发现很多同学对Service层和Manager层的区别并不是特别的清楚,即使清楚的同学大部分也并没有完全遵守手册中的规范,这种现象导致Manager层除了沉底一些通用能力以外和Service层并没有什么本质区别。

在实际的业务代码中Service层和Manager层都充斥了大量的第三方依赖,对系统的稳定性有很大的影响。每依赖一个第三方服务都要各种异常问题,这些异常处理的代码往往会和业务代码混在一起,当这种代码多了以后会使代码的可读性非常差。

阿里文娱业务的复杂度提升很快,业务迭代速度也很快, Service层和Manager层代码量迅速膨胀,业务逻辑变得越来越复杂。在这种业务场景下,大文娱引入了领域驱动设计并设计了一套完整的领域驱动模型评估与演进的解决方案来辅助开发同学将领域驱动设计的思想真正的落地。

四 文娱领域驱动设计实践

领域驱动设计的关键在于识别业务的模型,而模型又是会随着业务的发展而演进的,对于新的业务来说能效平台提供了业务模型分析的功能,开发同学可以在能效平台设计并搭建自己的领域模型,搭建出来后能效平台可以评估领域模型设计的是否合理,如果模型设计合理则可以基于以上设计的模型符合领域模型规范的代码。对于已有应用,能效平台设计了一套领域注解并以SDK的形式提供出去:

  • 第一步:开发同学按照领域设计的原则对业务代码进行分析并打上注解。
  • 第二步:能效平台可自动扫描该项目并收集该项目中的领域模型。
  • 第三步:模型收集后,开发同学可以在能效平台改进业务模型并重新按照领域模型的规范生成代码。

完整流程如下图所示:

领域驱动设计详解:是什么、为什么、怎么做?

1 模型采集

对于已有的准备重构的应用,我们设计了一套领域模型的注解,开发同学可以将注解加到对应的类、属性、方法上。当系统是按数据模型落地而不是按领域模型的方式落地时,可以先找到系统的数据模型,然后在能效平台对数据模型进行组织生成领域模型。

领域驱动设计详解:是什么、为什么、怎么做?

2 模型搭建

对于新应用或者已经进行完模型采集的应用,开发同学可以在能效平台进行模型的搭建和修改,如图6所示。

领域驱动设计详解:是什么、为什么、怎么做?

3 健康度评估

对于已经搭建完的模型能效平台,根据领域驱动设计的规范创建了一套完整的校验规则,模型搭建完成在生成脚手架之前会根据校验规则进行打分,当打分通过时可以将模型生成脚手架。

领域驱动设计详解:是什么、为什么、怎么做?

4 脚手架生成

当模型搭建完毕并且校验通过后可以将模型生成脚手架,其代码结构是按照六边形架构的标准生成的,六边形架构也成为端口与适配器架构,该架构的思想是将内部核心的领域逻辑与外界依赖进行隔离,这里的依赖是指所有对其他微服务的依赖、http的依赖、数据库依赖、缓存依赖、消息中间件依赖等等,所有的这些依赖都通过适配器进行转换成应用可理解可识别的最小化信息。

在实际的项目中,每种依赖都要考虑各种异常情况并进行处理,而这些处理实际上并不数据领域逻辑,却耦合到了业务代码里,当这种依赖多了对系统的稳定性会产生很大的影响,传统的分层架构虽然也会让我们将自身的领域逻辑和依赖进行分离,在阿里巴巴规范手册中提到所有的依赖都应该放到Manager层,但是这种规范是非常容易被打破的。六边形架构从应用分层上让我们更容易去遵守这样的规范。

领域驱动设计详解:是什么、为什么、怎么做?

根据六边形架构的指导思想,在实际的应用分层中一般划分为四层,分别是:

  • 用户接口层:负责用户展现相关的逻辑。
  • 应用层:负责对一个用例进行流程编排(将接口用例分成若干个步骤,但是不负责每步的具体实施)。
  • 领域层:负责实现核心的领域逻辑即业务逻辑(负责实现具体的业务逻辑)。
  • 基础设施层:所有依赖的具体实现。

但是从应用架构的角度看,层级组织形式可以分为两种:

传统分层架构

如图9左侧,这种分层架构是Evic Evans在《Domain-Driven Design : Tackling Complexity in the Heart of Software》中提出的,其中用户接口层、应用层、领域层可直接依赖基础设施层,与图3的传统架构并无本质区别,因为所有层都直接依赖了基础设施层。这种方式需要强制开发同学将所有的依赖进行下沉,随着时间的推移这种规范非常容易被打破。

领域驱动设计详解:是什么、为什么、怎么做?

依赖倒置的分层架构

如图9右侧,这种分层架构是依赖倒置的分层架构,特点是:

  • 基础设计层可直接依赖其他三层,反之则不行。
  • 用户接口层、应用层、领域层如果要使用基础设施层中的能力,只能通过IOC的方式进行依赖注入,这也遵从了面向对象编程中的依赖倒置原则。

当开发同学要在以上三层中直接引用第三方依赖时,是找不到具体的类信息的,也就是不能import。同时这种方式对单元测试的规范也可以起到很大的作用,当我们编写单元测试时可以为领域层注入一个测试运行时的依赖,这样应用运行单元测试可以不依赖下游服务,在代码层面上也更加规范。

五 总结

经典的三层或多层架构虽然是目前最普遍的架构,但是在隔离方面做得并不够好。在业务架构选型时要结合自身业务特点,而不能千篇一律的选择某一种业务架构,合适的业务架构可以延长项目的生命周期,降低项目的重构频率,最终达到降低人力成本的目的。