awakeBird Back-end Dev Engineer

关于分层的一些探索

2019-07-24

188-800x300.jpg 在应对复杂项目设计时,分层几乎是必须用到的手段,那回想自己启动一个新项目时是如何确定分层的呢?是直接 copy 前人项目骨架,还是参照现有的理论模型(MVCDDDCQRS等)进行实现呢?其实,分层作为偏方法论的一种设计,不同的人有不同的理解,即使采用相同的分层模型,对模型中一些概念的认知也存在偏差,这篇文章也是简单谈谈我对分层模型的初步探索。

分层的意义

这里先多嘴谈谈自己对分层的认知,刚开始写代码的时候,对分层的理解也只停留在把类似功能的代码写在一起的程度。之后开始工作,分层则变为了自己整洁代码的实现方式,甚至会对很多上千行的代码文件进行拆解,放在不同的「层」中,到了今天有了两年工作经验以后,对分层的理解才逐渐清晰起来。

分层是从更高的层面来分解和简化问题的一种方式,确定一种分层方式,对应的每层职责、各个层该包含的内容、层与层之间的依赖关系也随之确定,带来的好处也是显而易见的,更低的耦合度、更高的灵活性和可扩展性、更清晰的依赖关系,为编码和测试提供了便捷途径。

如何分层

虽然分层的意义很明显,但说回分层的本质,也只是根据某些规则对对象进行分类,并确定不同类(层)之间的依赖关系。明确这个主旨之后,如何分层其实是很灵活的,基于不同的视角我们可以有不同的分类规则(比如即可以按照业务进行划分,又可以按照技术组件进行划分),基于不同的复杂程度我们也可以适当增加或删掉一些分层(比如在项目规模比较小时可以把应用层和领用层合为一层)。

但不论怎样分层,本质上都是一种约定,在制定分层规范时需要把握一些概念。

分类标准

通常情况下,我们可以把系统划分为变化较大的业务部分和相对稳定的技术部分,业务部分又可以分为展示层逻辑层,展示层又可以分为页面部分接口部分,经过这样不断的细分与抽象,我们可以不断进行更细粒度的分类迭代。

  • 业务
    • 展示层( Presentation Layer / User Interface Layer / View Layer )
      • 数据层( web )
      • 接口层( interface / api )
    • 内部逻辑层
      • 应用层/服务层(Application Layer / Service Layer / Controller Layer )
      • 逻辑层/领域层(Business Layer / Business Logic Layer / Domain 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.基础设施层

基础设施层为上层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件。

基础设置层包括独立于应用程序之外的所有对象,包括外部库、数据库引擎、第三方服务调用、消息后端等

小结

76497cf6ff65c0b1b8e98ffbd74f08ca.png

由此产出的骨架结构如下:

└── 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 模型大同小异,这里就不做展开了。

ports-and-adapters.jpg

结语

在我的理解中,不同的分层模型更有优劣,DDD 模型更突出业务领域,但会引入额外的学习和开发成本,三层模型会随着业务量增加而变得臃肿,但简单易用,提高了开发效率。各种分层没有绝对意义上的好坏之分。与为使用哪种分层模型争得面红耳赤相比,更为有效的方式是将分层作为一种规范,在开发之初就制定下来。

( End )


Comments

Content