一般初创软件,为快速上线,几乎不考虑分层。但随业务越发复杂,就会导致逻辑复杂、模块相互依赖、代码扩展性差等各种问题。
架构分层迫在眉睫。
1 什么是架构分层?
软件工程中常见的设计方式,将整体系统拆分成N个层次,每个层次有独立的职责,多个层次协同提供完整的功能。
初学 JavaWeb 时一般要求设计成 MVC 架构。另外一种常见的分层方式是将整体架构分为
- 表现层(Web)
展示数据结果和接受用户指令的,是最靠近用户的一层; - 逻辑层(Service)
复杂业务的具体实现; - 数据访问层(Dao)
主要处理和存储之间的交互。
这就可以隔离关注点,让不同的层专注做不同的事情。其它分层案例,比如OSI网络七层模型,TCP/IP协议网络四层模型。
2 分层有什么好处?
简化设计
各司专职,而不必将自己活成全才。
高复用
比如在设计某系统时,发现某层具有通用性,就可把它抽取独立出来,在设计其它系统时使用。
横向扩展
可以让我们更容易做横向扩展。如果系统没有分层,当流量增加时我们需要针对整体系统来做扩展。但是,如果我们按照上面提到的三层架构将系统分层后,就可以针对具体的问题来做细致的扩展。
比如业务逻辑里面包含有比较复杂的计算,导致CPU成为性能的瓶颈,那这样就可以把逻辑层单独抽取出来独立部署,然后只对逻辑层来做扩展,这相比于针对整体系统扩展所付出的代价就要小的多了。
架构分层究竟和高并发设计的关系是怎样的?我们知道横向扩展是高并发设计思想之一,既然架构分层可方便横向扩展, 那么高并发系统一定是分层的。
3 如何架构分层?
关键在于理清层次边界。
若按三层架构分层,边界不就很容易界定吗?
是的没毛病,当业务逻辑简单时,层次间边界的确清晰,开发新功能时也知道把代码往哪写。但当业务逻辑越来越复杂,边界也会变得模糊。
3.1 案例
任一系统都有用户模块,比如返回用户信息的接口,它调用逻辑层的GetUser方法,GetUser方法又和User DB交互获取数据,就像下图左边展示的样子。
这时,产品提出一个需求,在APP中展示用户信息的时候,若用户不存在,那么要自动给用户创建一个用户。同时,要做一个HTML5页面保留之前的逻辑,即不需要创建用户。这时逻辑层的边界就变得不清晰,表现层也承担了一部分的业务逻辑(将获取用户和创建用户接口关联)。
3.2 主流分层职责
MVC架构的简单性让太多的人觉得项目工程结构理所应当就是这样的,然后呢,一大堆的业务逻辑就随意的堆砌在了service中,对象啥的,只是单纯的数据传输作用,出现了用面向对象的语言,写面向过程的程序的普遍现象。按照领域驱动设计的思路,最重要的还要有领域模型层。当然manage层这种方案也是一种思路,但是我觉得,这种方式,还不够,必须有清晰的业务模型和合理的分层结构配合,才能更好的提现分层的作用。
终端显示层
各端模板渲染并执行显示的层。当前主要是 Velocity/framemaker渲染、js渲染、移动端展示等
开放接口层
将Service层方法封装成开放接口,同时进行网关安全控制和流量控制等
Web层
主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等
Service层
业务逻辑层。调用manager层和dao层处理业务,即简单的业务可以不抽取manager,直接调用dao。
业务类的校验放在service层,一般性的参数校验可以放在web层,这可以通用化。
Manager 层(通用业务处理层)
DDD中也叫领域层。
- 可以将原先Service层的一些通用能力下沉到这一层,比如与缓存和存储交互策略,中间件的接入。业务逻辑放在manager,service来编排manager的原子服务。
- 也可在这一层封装对第三方接口的调用,比如调用支付服务,调用审核服务等
该分层架构相比MVC主要就是增加了Manager层,它与Service层的关系:Manager层提供原子的服务接口,Service层负责依据业务逻辑来编排原子接口。
- 业务逻辑就是你的产品逻辑,比如创建用户要具体做什么。
- manager层的原子服务指的是实现单一功能的服务。
- 事务应该在service层
DAO层(数据持久层)
数据访问层,与底层 MySQL、Oracle、HBase 等进行数据交互。缓存可以放在存储层。
外部接口或第三方平台
包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口
例如,Manager层提供创建用户和获取用户信息的接口,Service层负责将这两个接口组装起来。这样就把原先散布在表现层的业务逻辑都统一至Service层,每层边界就清晰了。
比如做一个接口,可将实现放在service层。之后公司内部调用逻辑可放在web层。而哪一天公司要开放这个接口,可直接新抽象一层(一个新的服务),即开放平台层!这样的好处是,可以将本司使用和第三方使用做隔离。比如在提供服务时,为了保证自家接口性能,对开放平台层做限流处理。
传统公司很多是分层部署的,比如保险和金融。service和dao部署在比较严密的网络区域,controller层部署在一个较宽松的网络区域,对外提供服务。等于在网络上增加了一个缓冲区,来保证服务的安全;而且可以通过单向网络规范层级调用,controller可以调用服务层,而服务层是不能调用web层的。
如果将数据访问层单独部署,比如拆分为单独的rpc服务,当然这样拆分粒度比较细。controller就是对外的门面,调用单独的服务层
- 可以为后期服务运维降低成本
- 可以提高数据访问层的复用度(数据访问层对外提供API,其他层的应用通过API方式与数据库进行交互),三来可以屏蔽各个数据库实现的具体细节。
3.3 层间依赖
数据流转只能在相邻层间。以三层架构为例,数据从表示层进入后一定要流转到逻辑层,做业务逻辑处理,然后流转到数据访问层和DB交互。但若业务逻辑很简单,可否从表示层直接到数据访问层,甚至直接读DB?
功能上是可以的,但从长远架构设计考虑,这会造成层级调用混乱,比如一旦DB地址变更,就需更改多层,这就失去了分层价值,且维护或重构都是灾难。
4 架构分层的缺陷
4.1 增加代码复杂度
原本可在接收请求后直接查询DB获得结果,却非要在中间多层设计,每层只简单地做数据传递。
有时增加一个小需求,可能还需更改所有层的代码,增加了开发成本,也增加了调试复杂度,增加了与其它模块负责人的沟通成本。
4.2 性能损耗
若每层独立部署,层间通过网络交互,那多层架构势必会在性能上有所损耗。
那是否还要选择架构分层呢?肯定的。
你要知道,任何的方案架构都是有优势有缺陷的,天地尚且不全何况我们的架构呢?分层架构固然会增加系统复杂度,也可能会有性能的损耗,但是相比于它能带给我们的好处来说,这些都是可以接受的,或者可以通过其它的方案解决的。我们在做决策的时候切不可以偏概全,因噎废食。
5 总结
分层架构是软件设计思想的外在体现,是一种实现方式。一些软件设计原则都在分层架构中有所体现。
比方单一职责原则规定每个类只有单一的功能,在这里可引申为每层拥有单一职责,且层与层之间边界清晰;
迪米特法则原意是一个对象应当对其它对象尽可能少的了解,在分层架构的体现是数据的交互不能跨层,只能在相邻层之间进行;
开闭原则要求软件对扩展开放,对修改关闭。它的含义其实就是将抽象层和实现层分离,抽象层是对实现层共有特征的归纳总结,不可修改,但具体实现可无限扩展,随意替换。
参考
- 《阿里巴巴Java开发手册》