大泥球式的代码
软件开发过程中,由于缺乏代码规划,没有组织,使得代码混在一起。 这样带来的问题: 1)没有把领域逻辑独立出来,导致代码保持与模型一致。
2)代码难理解,应该内聚的逻辑散在不同的地方,应该解耦的代码混在一起
3)技术代码/业务代码混在一起,可维护性差
4)代码重复,可维护难度越来越大
以上就是大泥球式代码。
分层架构 - 解决大泥球式代码的最佳实践
分层架构把代码分成若干层,每层负责不同的关注点。图里的箭头表示依赖关系,这里的意思是只能外层依赖内层,内层不能依赖外层。
根据软件架构中的一个重要原则:代码中不稳定的部分,应该依赖稳定的部分。所以,分层架构中越是内层,就越稳定,越是外层,相对就越容易变化。
分而治之。
怎么划分代码的层次
分离领域 - 领域层
领域层处于我们架构的最内层,是整个系统的核心,是 DDD 的基本理念。
模块级:在 domain 包里,我们要根据领域模型中的模块进一步分包。这样,就保证了在模块一级代码和模型的一致性。下面这张图包含了领域层和模块的程序结构:
实体级:按模块分包以后,我们接着按照领域模型,在模块包中建立实体类,这样就能在类的层面和模型保持一致了。这里先为每个类写一个“空壳”。
给领域一个门面 - 应用层
在领域层外面再加一层,DDD 和六边形架构都将这一层称为 Application,也就是应用层。
这一层主要负载的逻辑:
1)接收外部请求
2)将领域层的处理结果封装为更简单的粗粒度对象,作为对外 API 的参数。这里说的粗粒度对象一般是 DTO(Data Transfer Object),也就是没有逻辑的数据传输对象,应用层负责 DTO 和领域对象的数据转换;
3)负责处理事务、日志、权限等等横切关注点。从设计模式的角度,这一层相当于“门面”(Facade)模式,
封装应用逻辑的类通常没有状态,只有方法,一般称为应用服务,我们可以用 XxxService 的形式来命名。下面就是增加了一些主要应用服务的代码结构:
用“适配器”处理输入输出
除了业务功能之外,程序里还有另一个重要的关注点——输入输出技术。我们的系统要和外界打交道,可以通过不同技术来实现,比如 Restful API、 RPC,以及传统的 Web 页面等等。
不论具体技术是哪一种,背后实现的业务功能很可能都是一样的。所以,输入输出技术和业务功能是两个不同的关注点。
六边形架构中将这层称为适配器,英文是 adapter。这是因为,这一层的目的是把业务功能“适配”到不同的输入输出技术。
说适配器层屏蔽了输入输出技术的差异,从而使应用层与具体技术无关,这样就达到了分离关注点的目的。
下图是增加了适配器层的代码结构:
Restful 包里是 Resful Api,web 包里面是传统的 JSP 页面。这些包里的适配器,在多数情况下,就是我们熟悉的 Controller。
用“适配器”处理数据持久化
数据处理持久化:包括数据库、缓存、文件系统、对象存储器。
DDD 里的另一个模式,叫做 Repository,中文可以叫仓库。这个模式用于封装持久化的代码,大体上类似于传统上说的 DAO(Data Access Object),也就是“数据访问对象”。 和 DAO 不同的是,仓库是以聚合为单位的,每个聚合有一个仓库,而 DAO 是以表为单位的,每个表有一个 DAO。
仓库和适配器有什么关系
我们需要一种适配器把具体的持久化技术和应用层以及领域层隔离开,而仓库就充当了这种适配器。 Controller 处理的是从外界向系统的调用,比如说来自 HTTP 客户端的调用;而仓库处理的是由系统向外界的调用,比如说对数据库的调用。也就是说,两者的方向不同。
在六边形架构里,把由外向内的适配器叫做 driven adapter,我把它译作被动适配器;而由内向外的适配器叫做 driving adapter,可以译作主动适配器。准确地说,主动适配器的作用不限于访问数据库,而是访问所有外部资源。
存放通用工具和框架 - common 层
工具类和框架,这些代码可能被前面的所有层依赖,那么是不是说,这些代码应该处于整个系统的最内层呢?如果这样做,那么和 DDD 所强调的以领域层为核心的思想就矛盾了。但如果不这么做,是不是又违反了层间依赖原则呢?
我们可以认为这些代码和前面说的各层根本不在同一个维度,它们是对各层代码起到公共的支撑作用的。用下面这张图比较容易说明这个思路:
框架和工具的区别一般是,框架会调用我们自己写的代码,而工具则被我们写的代码所调用。
分层架构的权衡
希望你能够理解分层架构背后的原理,然后针对自己项目中存在的痛点进行权衡,形成适合自己项目的架构规范。 在实际项目中,有些层次可以合并,有些层次则可以分得再细。在下面的表列出了几种分层架构的变化:
极客时间《手把手教你落地DDD》学习笔记 Day9