架构师修炼系列【分层架构】

分层架构是很常见的架构模式,它也叫N层架构,通常情况下, N至少是2层。例如,C/S架构、B/S 架构,常见的是3层架构(例如, MVC 、 MVP架构)、4 层架构,5层架构的比较少见, 一般是比较复杂的系统才会达到或超过5层,比如操作系统内核架构

分层架构类型

按照分层架构进行设计时,根据不同的划分维度和对象,可以得到多种不同的分层架构

  • C/S架构、 B/S架构
    划分的对象是整个业务系统,划分的维度是用户交互,即将和用户交互的部分独立为一 层,支撑用户交互的后台作为另外一层。例如, C/S 结构

在这里插入图片描述

  • MVC 架构、 MVP 架构
    划分的对象是单个业务子系统,划分的维度是职责,将不同的职责划分到独立层,但各
    层的依赖关系 比较灵活 。 例如, MVC 架构中各层之间是两两交互的

在这里插入图片描述

  • 逻辑分层架构
    划分的对象可以是单个业务子系统,也可以是整个业务系统,划分的维度也是职责。虽然都是基于职责划分,但逻辑分层架构和MVC架构、MVP架构的不同点在于,逻辑分层架构中的层是自顶向下依赖的典型的有操作系统内核架构、TCP/IP 架构,例如,Android 操作系统架构
    在这里插入图片描述
    典型的 J2EE 系统架构也是逻辑分层架构
    在这里插入图片描述

分层架构详解

无论采取何种分层维度,分层架构设计最核心的一点就是需要保证各层之间的差异足够清晰,边界足够明显,让人看到架构图后就能看懂整个架构,这也是分层不能分太多层的原因,否则如果两个层的差异不明显,就会出现程序员A认为某个功能应该放在A层,程序员B认为应该放在B层,如此分层不清晰,这样的架构进入实际开发落地就失去了分层的意义

分层架构之所以能够较好地支撑系统扩展,本质在于:隔离关注点(separation of concerns), 即每个层 中的组件只会处理本层的逻辑 。 比如说,展示层只需要处理展示逻辑,业务层中只需要处理业务逻辑,这样我们在扩展某层时,其他层是不受影响的,通过这种方式可以支撑系统 在某层上快速扩展 。例如, Linux 内核如果要增加 一个新的文件系统,则只需要修改文件存储 层即可,其他内核层无须变动

当然,并不是简单地分层就一定能够实现隔离关注点从而支撑快速扩展,分层时要保证层与层之间的依赖是稳定的,才能真正支撑快速扩展 。 例如, Linux内核为了支撑不同的文件系 统格式,抽象了VFS 文件系统接口,架构图如下
在这里插入图片描述
如果没有VFS,只是简单地将ext2、ext3、reiser等文件系统划为“文件系统层”,那么这个分层是达不到支撑可扩展的目的的。因为增加一个新的文件系统后,所有基于文件系统的功能都要适配新的文件系统接口,而有了VFS后,只需要VFS适配新的文件系统接口,其他基于文件系统的功能是依赖VFS的,不会受到影响

对于操作系统这类复杂的系统,接口本身也可以成为独立的一层。例如,我们把VFS独立为一层是完全可以的。而对于一个简单的业务系统,接口可能就是Java语言上的几个interface定义,这种情况下如果独立为一层,看起来可能就比较重了
例如,经典的J2EE分层架构中,Presentation Layer 和 Business Layer 之间如果硬要拆分一个独立的接口层, 则显得有点多余了
分层结构的另外一个特点就是层层传递,也就是说一旦分层确定 , 整个业务流程是按照层进行依次传递的,不能在层之间进行跳跃。最简单的C/S结构,用户必须先使用C层,然后C层再传递到S层,用户是不能直接访问S层的。传统的J2EE4层架构,收到请求后,必须按照如下围所示的方式传递请求
在这里插入图片描述
分层结构的这种约束,好处在于强制将分层依赖限定为两两依赖,降低了整体系统复杂度,例如,Business Layer被Presentation Layer依赖,自己只依赖 Persistence Layer。但分层结构的代价就是元余,也就是说,不管这个业务有多么简单,每层都必须要参与处理,甚至可能每层都写了一个简单的包装函数。我们以用户管理系统最简单的一个功能“查看头像”为例 。查看头像功能的实现很简单,只是显示一张图片而己,但按照分层架构来实现,每层都要写一个简单的函数。简略代码如下

Presentation Layer:
package layer;
public class AvatarView{
    
    
	public void displayAvatar(int userId){
    
    
		String url = AvatarBizz.getAvatarUrl(userId);
		// 渲染代码
		return;
	}
}
Business Layer:
package layer;
public class AvatarBizz{
    
    
	public static String getAvatarUrl(int userId){
    
    
		return AvatarDao.getAvatarUrl(userId);
	}
}
Persistence Layer:
package layer;
public class AvatarDao{
    
    
	public static String getAvatarUrl(int userId){
    
    
		// 省略具体实现代码,正常情况下可以从MySQL数据库中通过userId查询头像URL即可
		return "https://avatar.csdnimg.cn/A/A/A/3_dawei_yang000000_1588350916.jpg"
	}
}

可以看出Business Layer的AvatarBizz类的getAvatarUrl方法和Persistence Layer的AvatarDao类的getAvatarUrl方法,名称和参数都一模一样,既然如此,我们是否应该自由选择是否绕过分层的约束呢?

  • 例如,“查看头像”的示例中,直接让AvatarView类访问AvatarDao类,不就可以减少AvatarBizz的冗余实现了吗?答案是不建议这样做,分层架构的优势就体现在通过分层强制约束两两依赖,一旦自由选择绕过分层,时间一长,架构就会变得混乱
  • 例如,Presentation Layer直接访问Persistence Layer, Business Layer直接访问Database Layer,这样做就失去了分层架构的意义,也导致后续扩展时无法控制受影响范围,牵一发动全身,无法支持快速扩展
  • 除此以外,虽然分层架构的实现在某些场景下看起来有些烦琐和冗余,但复杂度却很低。例如,样例中AvatarBizz的getAvatarUrl方法,实现起来很简单,不会增加太多工作量

分层架构另外一个典型的缺点就是性能,因为每一次业务请求都需要穿越所有的架构分层, 有一些事情是多余的,多少都会有一些性能的浪费 。当然,这里所谓的性能缺点只是理论上的分析,实际上分层带来的性能损失,如果放到20世纪80年代,可能很明显,但到了现在,硬件和网络的性能有了质的飞越,其实分层模式理论上的这点性能损失,在实际应用中,绝大部分场景下都可以忽略不计

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/108571254