在应对复杂项目设计时,分层几乎是必须用到的手段,那回想自己启动一个新项目时是如何确定分层的呢?是直接 copy 前人项目骨架,还是参照现有的理论模型(MVC
、DDD
、CQRS
等)进行实现呢?其实,分层作为偏方法论的一种设计,不同的人有不同的理解,即使采用相同的分层模型,对模型中一些概念的认知也存在偏差,这篇文章也是简单谈谈我对分层模型的初步探索。
分层的意义
这里先多嘴谈谈自己对分层的认知,刚开始写代码的时候,对分层的理解也只停留在把类似功能的代码写在一起的程度。之后开始工作,分层则变为了自己整洁代码的实现方式,甚至会对很多上千行的代码文件进行拆解,放在不同的「层」中,到了今天有了两年工作经验以后,对分层的理解才逐渐清晰起来。
分层是从更高的层面来分解和简化问题的一种方式,确定一种分层方式,对应的每层职责、各个层该包含的内容、层与层之间的依赖关系也随之确定,带来的好处也是显而易见的,更低的耦合度、更高的灵活性和可扩展性、更清晰的依赖关系,为编码和测试提供了便捷途径。
如何分层
虽然分层的意义很明显,但说回分层的本质,也只是根据某些规则对对象进行分类,并确定不同类(层)之间的依赖关系。明确这个主旨之后,如何分层其实是很灵活的,基于不同的视角我们可以有不同的分类规则(比如即可以按照业务进行划分,又可以按照技术组件进行划分),基于不同的复杂程度我们也可以适当增加或删掉一些分层(比如在项目规模比较小时可以把应用层和领用层合为一层)。
但不论怎样分层,本质上都是一种约定,在制定分层规范时需要把握一些概念。
分类标准
通常情况下,我们可以把系统划分为变化较大的业务部分和相对稳定的技术部分,业务部分又可以分为展示层和逻辑层,展示层又可以分为页面部分和接口部分,经过这样不断的细分与抽象,我们可以不断进行更细粒度的分类迭代。
- 业务
- 展示层( Presentation Layer / User Interface Layer / View Layer )
- 数据层( web )
- 接口层( interface / api )
- 内部逻辑层
- 应用层/服务层(Application Layer / Service Layer / Controller Layer )
- 逻辑层/领域层(Business Layer / Business Logic Layer / Domain Layer )
- 展示层( Presentation Layer / User Interface Layer / View Layer )
- 技术
- 数据访问层( Data Access Layer )
- 日志、安全、异常
依赖关系
根据层与层之间的依赖关系可以引出两种典型的分层风格:
- 严格分层:每一层只能访问同层和它的直接下层。这样直接降低了层与层之间的耦合程度,修改其中一层不会对整个系统产生广泛的影响。但会造成无意义的代码。
- 灵活分层:每一层都可以访问同层和它的所有下层。无需引入过多的转发代码,但可能引发混乱。
这里的一般实践是先引入严格分层,只有当其中一层大多数代码没有任何业务逻辑时,才考虑重构或开放下层。
分层与分割
对于一个系统来讲,分层是一种横向的划分,而分割是纵向的划分,业务中往往需要两种方式结合对系统进行分解。
但分层与分割涉及到先后关系,不同分层模型对这一点的约束不同,DDD 推荐的方式是先按功能进行分割,再在每个功能模块中进行分层,这样业务功能相对比较内聚,每个业务功能的每一层都不会很重,也利于之后的拆分或者服务化。
分层模型
如上文所说,分层模型有一些现有的理论支撑,这里列举几种常用的分层模型进行介绍。
MVC
MVC 架构将程序代码分为三层:
- 视图层 View:展示界面
- 数据层 Model:操作的数据或信息,包含业务逻辑和规则
- 控制层 Controller:负责调度 View 和 Model 层响应请求
MVC 架构将各层的职责进行划分,降低了各层间的耦合,增强了各层的可复用和可测试性,几乎所有的编程语言都提供了强大的 MVC 框架,在传统 Web 时代大显身手,但目前的前后端分离模式使得 View 层几乎灭绝,因此目前后端采用的大多是下面的三层架构。
三层架构
三层架构同样将程序划分为三层:
- 表现层:UI 层,展示给用户的界面,或者对外提供的接口 api
- 业务逻辑层:BLL 层,对 api 的实现,对业务逻辑和规则的处理
- 数据访问层:DAL 层,对数据库的操作
这是最经典常用的分层模型,我之前所基础到的大部分项目都采用这一模型或者相应变形,大致代码结构如下:
|____api
| |____dto
| |____enums
| |____HelloWorldService.java
|____service
| |____impl
| |____soa
| |____util
| |____constans
|____dal
| |____domain
| |____dao
| |____manager
- api 层中定义接口,属于 UI 层。
- 在 service 层中实现接口,调用 dal 层或其他服务的 facade 对象完成业务逻辑后返回,属于 BLL 层
- dal 层中又细分出 manager 对象进行一些数据层面的聚合
虽然都是三层,但这里的 UI 层相当于兼并了 MVC 中 View 层和 Controller 层的功能,BLL 和 DAL 层则共同承担了 Model 层的功能,有些项目骨架会把 BLL 层额外再进行细分,分为 biz(业务逻辑层),sal(外部调用封装),task(woker 任务逻辑层),但按职能仍属于 BLL 层,属于三层架构的范畴。
这种分层模型结构清晰简洁,最明显的优势在于没有太多学习成本,后人可直接上手修改。相应的会带来下面的一些问题
- BLL 层集中了太多的业务逻辑,业务量大起来之后会变得很臃肿,难以测试和维护
- 三层中的大部分对象都属于贫血模型,没有对业务规则的封装,容易产生重复代码
DDD 架构
领域驱动设计(Domain-Driven Design)由三层架构演变而来,其经典分层架构如下
1.用户界面层/标识层(User Interface Layer)
这里的用户一般不仅指直接与展示界面交互的人,更多时候指的是外部系统,该层包括所有与其他应用交互的接口和通讯设施。
它的只能主要由两部分:
- 输入参数的解释、验证以及转化
- 输出参数的序列化
这里的参数校验为粗粒度的参数校验(判空、日期判断等),包含业务逻辑的参数校验在领域层。
一般来说,这层包括 web 服务( Controller / Servlet )或 RPC 接口,rpc 接口包括以下内容:
- Facade:远程外观,不含业务逻辑
- DTO:数据传输对象
- Assembler:负责传输对象与领域对象的相互转化,不对外暴露
2.应用层(Application Layer)
这层定义了软件要完成的功能,并且指挥对应的领域对象解决问题,是与其他系统进行交互的必要通道。
应用层要尽量简单,不含任何的业务规则和知识,只为下层对象协助、委托任务。它主要负责组织整个软件的流程,是面向用例设计的。
应用层的基础组件是 Service,为了协调各组件工作,通常会与多个组件交互,如其他 Service、领域对象、Repository 等。
这里与三层模型中的 Service 有显著区别。
3.领域层(Domain Layer)
主要负责表达业务概念、业务状态和业务规则,是整个系统的核心,几乎全部的业务逻辑在该层实现。
领域层主要包括以下组件:
- Entity:具有唯一标识的实体对象
- Value Object:值对象,无需唯一标识
- Domain Service:领域服务,无法归类到某一对象上的行为,一般是一些操作,而非事物。
- Aggregate:聚合根,一组有内聚相关关系的对象的集合,每个聚合都有一个 root 和 boundary
- Factory:工厂,创建复杂对象,隐藏创建细节
- Repository:仓储,以接口形式给出查找和持久化对象的方法
每个组件的具体含义可以参照《领域驱动设计》一书,前三个组件很好理解,关于 Aggregate、Factory 和 Repository 的引入初衷是,在复杂的关联关系中对象的管理和数据的一致性很难保证,所以使用 Aggregate 来管理一组相关的对象,可以降低对象使用的复杂性。同时也会造成对象的创建和持久化比较复杂,因此引入 Factory 和 Repository。
Respository 是不同于 DAO 的,DAO 是以数据库的角度在看问题,并且提供 CRUD 操作(只是对数据库的一个封装),是一种面向数据的处理方式。而 Respository 则更加面向对象,用于领域模型中,避免数据访问层的暴露破坏对象的封装。
4.基础设施层
基础设施层为上层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件。
基础设置层包括独立于应用程序之外的所有对象,包括外部库、数据库引擎、第三方服务调用、消息后端等。
小结
由此产出的骨架结构如下:
└── demo
├── interfaces
│ └── [module]
│ ├── facade
│ │ ├── xxxServiceFacade.java
│ │ ├──dto
│ │ │ └── xxxService.java
│ ├── web
│ │ ├── xxxController.java
├── application
│ ├── XxxService.java
│ ├── impl
│ │ └── XxxServiceImpl.java
│ └── util
│ └── XxxUtil.java
├── domain
│ ├── model
│ │ └── [module]
│ │ │ ├── xxx.java
│ │ │ ├── xxxRepository.java
│ └── service
│ └── XxxService.java
└── infrastructure
├── messaging
│ └── jms
│ └── XxxConsumer.java
├── persistence
│ └── mysql
│ └── XxxRepositoryImpl.java
└── [module]
└── Xxx.java
六边形模型
DDD 模型中存在领域层与基础设施层相互依赖的情况,这时稍作改进,将基础设施层改为 Adpter 模式,再加入其它的对称元素,就形成了六边形适配模型,与 DDD 模型大同小异,这里就不做展开了。
结语
在我的理解中,不同的分层模型更有优劣,DDD 模型更突出业务领域,但会引入额外的学习和开发成本,三层模型会随着业务量增加而变得臃肿,但简单易用,提高了开发效率。各种分层没有绝对意义上的好坏之分。与为使用哪种分层模型争得面红耳赤相比,更为有效的方式是将分层作为一种规范,在开发之初就制定下来。
( End )