系统架构:分层架构

news2024/9/25 17:11:03

引子

系统在从0到1阶段时,为了可让产品快速上线,此时系统分层一般不是软件开发需要重点考虑的范畴,但是随着业务逐渐复杂 ,大量代码纠缠耦合,此时会出现逻辑不清楚、模块相互依赖、扩展性差、改一处动全身的问题。

系统在从1到100…0时,系统会变的特别复杂,此时系统分层就会被提上日程,那么什么是分层?为何要分层?如何对软件进行分层?软件分层有什么指导原则呢?本文将带大家逐一寻找答案。

何为分层架构?

分层架构是一种分而治之的设计思想,是一种基于层次的架构范式。一般开发人员会把一个系统基于分而治之的理念将系统拆分成多个模块或子系统;然后对已拆分出的模块按业务层次分成相关依赖和层次关系的组;从而实现根据功能组实现功能和耦合的隔离。我们称这种范式为分层架构范式。

分层架构举例

图1 Android系统架构图
图2 Android架构层间关系图

分层架构最典型的例子即是Android操作系统,其是基于Linux的移动开源操作,Android系统采用自上而下的分层范式,图1是google官方提供Android系统架构图,图2是Android系统的五层层间关系图。

Android操作系统架构的五个层分别为:

  • 应用层 applications
  • 应用框架层 frameworks
  • 系统运行库层 native c/c++ libraries/android runtime
  • 硬件抽象层 hardware abstraction layer
  • Linux内核层 linux kernel

为何分层?

在软件开发从0到1阶段,开发人员一般通过模块隔离实现业务隔离,此时模块隔离是软件隔离的“专注点”;而在系统从1到100…0时,由于系统处于超大规模状态,按照模块隔离已不太现实。此时几乎每个软件都通过层隔离“关注点”,以此应对不同需求的变化,使得这种变化可以独立进行,从而给开发者带来如下的好处:

  • 高内聚:分层设计可以简化系统设计,让不同的层专注自身业务
  • 低耦合:层与层之间通过接口或API交互,依赖方不用知道被依赖方的细节
  • 复用:分层之后可以做到很高的复用
  • 扩展性:分层架构可以让我们更容易做横向扩展

除此之外,分层架构范式还是隔离业务复杂度与技术复杂度的利器,《领域驱动设计模式、原理与实践》有这样一段论述:

为了避免将代码库变成大泥球并因此减弱领域模型的完整性且最终减弱可用性,系统架构要支持技术复杂性与领域复杂性的分离。引起技术实现发生变化的原因与引起领域逻辑发生变化的原因显然不同,这就导致基础设施和领域逻辑问题会以不同速率发生变化。

这里的“以不同速率发生变化”,其实就是引起变化的原因各有不同,这正好是单一职责原则(Single-Responsibility Principle,SRP)的体现,这也是为什么要将业务与基础设施分开的原因,因为引起它们变化的原因不同。

综上所述,分层范式本质就是将复杂问题简单化,基于单一职责原则让每层代码各司其职,基于“高内聚,低耦合”的设计思想实现相关层对象之间的交互。从而提升代码的可维护性和可扩展性。在小规模软件中,职责隔离的关注点是模块或类,在大规模软件设计中,职责隔离的关注点是层次,我们通过层次隔离变化,这才是软件分层的终极目标。

如何分层?

软件的分层存在诸多原则,此处笔者首先分析介绍软件分层的四个原则,然后为大家介绍《面向模式的软件架构:卷1(模式系统)》提出的一种逐步细化的分层架构方法。

指导原则

之所以需要对软件进行分层,这其实是我们下意识的认知规则:机器为本,用户至上。机器是软件运行的基础,而我们打造的系统是为用户服务的。分层架构层次越高,其抽象层次就越面向业务,越面向用户;分层架构层次越往下,其抽象层次就越变的统一,越面向设备。经典的三层架构就是源于这个认知规则:上层关注用户的体验和交互;中层关注应用和业务逻辑;下层关注外部资源和设备。因此分层的第一个原则就是基于关注点为不同的业务划分层次。

分层的第二个原则是隔离变化,分层时针对不同的变化原因确定层次的边界,至少保证将变化对各层的影响限制到最小,但是这只是最小目标,最大目标是严禁层间相互干扰。例如,数据库的修改应该只影响基础设施层的数据模型和领域层的领域模型,但是如果只修改基础设施层的数据库访问逻辑,就不应该影响到领域层的领域模型。

分层的第三个原则是层与层之间应该是正交的,所谓正交,并非指两层之间没有关系,而是两者应该是垂直相交的两条直线。两次之间唯一的依赖点就是两条直线的交点,即两层之间的协作点。此协作点就是层间的抽象接口。正交的两条直线,无论对那条直线进行延展,都不会对另外一条直线产生任何影响(直线的投影)。如果非正交,一条直线延伸时,它总会投影到另外一条直线,这就意味着另外一条直线受到了本条直线延展的影响。

分层还有一个原则是保证同一层的组件处于同一个抽象层次。此原则借鉴了 Kent Beck 在《Smalltalk Best Practice Patterns》 一书提出的“组合方法”模式。该模式要求一个方法中的所有操作处于相同的抽象层,这就是所谓的“单一抽象层次原则(SLAP)”。

分层指导

了解了诸多分层原则,下面为大家介绍《面向模式的软件架构:卷1(模式系统)》提出的一种逐步细化的分层架构方法。

第一步,定义将任务划分到不同层的抽象准则。在真正的软件开发中,通常根据距离硬件的距离划分较低的层,按照概念的复杂度划分比较高的层。

第二步,根据抽象准则确定抽象层的级数。

第三步,给每层命名并分派任务。

第四步,规范服务,确保任何组件或模块不会跨层。同时将更多的组件放置到较高的层次,让较低的层次保持苗条。从而形成倒金字塔。

第五步,完善层次划分,反复执行第一到四步。

第六步,规范每一层的抽象接口,确保对J+1层而言,J层应该是一个“黑盒”。设计统一的接口,该接口可以提供第J层的所有服务。

第七步,确定各层的结构。确定层次得结构不仅要确保层间关系要合理,同时要求层内组件要有适当的颗粒度。从而避免层间关系完美无暇,但是层内关系混乱的局面。

第八步,规范相邻层的通信机制,分层架构中,常用的通信机制就是推模型,第J层调用第J-1层时随服务调用一起传递所以所需的信息。

第九步,将相邻层解耦,一般情况下,分层架构中上层知道下层,但是下层不知道上层的身份,从而形成单向耦合。

层间协作

在大家的固有认知中,分层架构都是自顶向下传递的,从抽象层次而言,抽象程度越高,越通用,越公共,此层次与具体业务隔离的越远。此层次就是我们平常所说的平台层或框架的调用者。

自顶向下要求上层依赖下一层,这是和依赖倒置原则(Dependency Inversion Principle,DIP)存在冲突的。依赖倒置原则要求:高层模块不应该依赖于低层模块,二者都应该依赖于抽象。这一原则给了我一个明确的提醒,谁规定的在自顶向下的架构中,依赖就要沿着自顶向下的方向传递,这是一个错误的理解。依赖倒置原则隐含的本质是:我们要依赖不变或稳定的元素(类、模块或层)。也就是该原则的第二句话:抽象不应该依赖于细节,细节应该依赖于抽象。

依赖倒置原则是“面向接口设计”原则的体现,即“针对接口编程,而不是针对实现编程”。高层模块对低层模块的实现是一无所知的,带来的好处是:

  • 低层模块的细节实现可以独立变化,避免变化对高层模块造成影响
  • 高层和底层模块都可以独立于对方单独编译,从而实现独立开发编译
  • 对于高层模块而言,低层模块的实现是可替换的

如果高层和低层都依赖抽象,那现在会存在这样一个问题,底层的具体实现怎么传递给高层呢?由于高层通过两者的“正交点”即抽象接口隔离的对具体实现的依赖,那么具体实现的依赖就转移到外部了,具体实现将由外部调用者决定。调用者在调用代码时才会把底层的具体实现传递给高层。软件开发大师Martin Fowler形象地将这种机制称为“依赖注入(dependency injection)。

因此,为了很好的解除高层对底层的依赖,我们需要将依赖倒置和依赖注入两种结合,从而更好的理解他们。

除此之外,层间的信息传递不一定都是自顶向下的传递,还有可能是自底向上的传递。例如Android系统中的通知机制。当Android系统收到消息时,Android系统将消息通知上层业务模块。如果说自顶向下的消息传递被描述为“请求“(或调用)”,那么自底而上的消息可被描述为“通知”。换个角度思考“通知”,这其实就是上层对下层的观察,下层的状态发生变化,通过观察机制将下层的变化传递给上层。而上层消费对下层传递的消息。此模式就是大家常说的观察者模式。

无论是通过依赖注入和依赖倒置原则实现上层对下层的“请求(或调用)”,亦或是通过观察者模式实现下层对上层的“通知”。这些都颠覆了我们固有思维中那种高层依赖低层的理解。

现在我们对层间协助有了更清晰的认知,所以我们在开发中要正视架构中各层之间的协作关系,打破高层依赖低层的固有思维,从解除耦合(或降低耦合)的角度探索层之间可能的协作关系。

另外,我们还需要确定分层的架构原则(或约束),例如是否允许跨层调用,即每一层都可以使用比它低的所有层的服务,而不仅仅是相邻低层。这就是所谓的“松散分层系统(Relaxed Layered System)”。

经典分层范式

经典的分层范式主要包括经典三层架构,阿里四层架构,DDD领域驱动分层架构三种架构方式。

经典三层架构

经典三层架构按照功能将系统功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)。表示层UI位于三层架构的最上层,实现系统与用户直接的交互,以及消息事件的处理;业务逻辑层BLL,实现数据处理和数据传递,将界面表示层和数据访问层连接起来,起到承上启下的作用;数据访问层DAL,实现数据的增加、删除、修改、查询等操作,并将操作结果反馈到业务逻辑层 BLL。

阿里四层架构

阿里四层架构,在原三层架构基础上增加了 Manager 层,将原经典三层架构细化成如图3所示的架构图

图3 阿里四层架构图

  • 开放接口层:
    1. 开放接口层可以直接依赖Service层也可依赖WEB层;
    2. 依赖Service层,可将 Service 封装成 RPC 对外暴露;
    3. 依赖WEB层,可以将业务封装成 HTTP对外暴露。
  • 终端显示层:
    1. 负责各个端的模板渲染并执行显示;
    2. 当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等;
  • Web层:
    1. 对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等;
  • Service层:
    1. 主要负责具体的业务逻辑服务实现;
  • Manager通用业务处理层,主要职责包括:
    1. 对第三方平台封装:对Service层通用基础组件的封装,如缓存、中间件通用处理;
    2. 与DAO层交互,对多个DAO的组合复用
  • DAO 层:
    1. 数据访问层,与 MySQL、Oracle、Redis等进行数据交互。

DDD分层架构

DDD领域驱动分层架构,领域驱动设计在经典三层架构的基础上进一步改良,在顶层用户界面层与业务逻辑层之间引入了应用层。引入应用层后,根据领域驱动的概念各层的名称也做了调整。将业务逻辑层更名为领域层,而将数据访问层更名为基础设施层(Infrastructure Layer),图4为 Eric Evans 在其经典著作《领域驱动设计》中的分层架构。

图4 DDD领域驱动分层架构

总结

现在,我们对分层架构有了更清晰的认识。因此我们必须打破那种谈分层架构必为经典三层架构又或领域驱动设计推荐的四层架构这种固有思维,而是将分层视为关注点分离的水平抽象层次的体现。既然如此,架构的抽象层数就不是固定的,甚至每一层的名称也未必遵循固有(经典)的分层架构要求。设计系统的层需得结合系统具体业务场景而定。当然我们也要认识到层次多少的利弊:过多的层会引入太多的间接而增加不必要的开支,层太少又可能导致关注点不够分离,导致系统的结构不合理。

分层架构范式,是软件开发中最常用,也是容易滥用的架构范式。开发人员可以根据当前软件的开发约束和需求,制订出符合自身的分层,以方便软件开发和开发人员协助。基于分层范式,我们可以做到修改模块内的代码,而不影响其他模块,这是软件分层给我们带来的优势。也是高内聚低耦合的特性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/175815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于双层共识控制的直流微电网优化调度(Matlab代码实现)

💥💥💥💞💞💞欢迎来到本博客❤️❤️❤️💥💥💥 🎉作者研究:🏅🏅🏅本科计算机专业,研究生电气学硕…

14. python运算符

Python 语言支持以下类型的运算符 1. 算术运算符 、-、*、/、%、**、// **  返回x的y次幂 //  取整除 - 向下取接近商的整数(//得到的并不一定是整数类型的数,它与分母分子的数据类型有关系) print(7//2) print(7.0//2) print(7//2.0)2. 比较(关系&…

进程概念——Linux

“技术是时间积淀出来的,你能速成的东西,别人也可以速成,所以需要耐心学习” 猛戳订阅🍁🍁 👉Linux操作系统详解👈 🍁🍁 这里是目录标题一、冯诺依曼结构为什么要存在内存&#xff1…

关系数据库-1-[mysql8]中的数据类型

详细介绍MySQL中的数据类型 1 MySQL中的数据类型 常见数据类型的属性,如下: 1.1 整数类型 一、类型介绍 整数类型一共有5种,包括TINYINT、SMALLINT、MEDIUMINT、INT(INTEGER)和 BIGINT。 二、可选属性 1、M:表示显示宽度 Q:…

【2022】年度总结——彼此当年少 莫负好时光

文章目录【2022】年度总结前言🎈🎈🎈2022的得与失🧨🧨🧨对2023的期望📧📧📧写在最后的话✍🏻✍🏻✍🏻【2022】年度总结 前言&#x1f…

client-go实战之七:准备一个工程管理后续实战的代码

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 系列文章链接 client-go实战之一:准备工作client-go实战之二:RESTClientclient-go实战之三:Clientsetclient-go实战之四:…

TCP为什么是三次握手和四次挥手以及可能出现的问题

目录TCP为啥设定为三次握手(两个角度分析)不可靠产生无效链接浪费服务器资源TCP为啥四次挥手服务端有剩余数据需要发送--四次挥手(多数情况)服务端无剩余数据发送--捎带应答--四次变三次(少数情况)四次挥手可能出现的问题可能出现大量的TIME_WAIT可能出现大量的CLOSE_WAITTCP为…

关于他人交谈?顺应张莉打破预期

如果你站在权利的优势场,其实你不用可以谋划什么,就可以直接顺利交流.这个时候打破预期,就要对别人更加尊重.例子: 美国前总统克林顿跟每一个在场的人员握手并询问名字。交流中弱势一方的预期是在,希望得到对方尊重,而不是工具人。如果站在劣…

文件没学懂没关系,我来教你快速学会文件

1. 什么是文件 文件通常是在磁盘或固态上的一段已经命名的存储区。C把文件看作是一系列连续的字节,每个字节都被单独读取。 在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的) 1.…

Spring Boot操作数据库学习之整合MyBatis

文章目录一 MyBatis简介二 配置数据源三 整合测试3.1 导入MyBatis依赖3.2 配置数据库连接信息application.yml3.3 使用idea测试数据库3.4 创建实体类【可以使用Lombok】3.5 创建实体类3.6 创建mapper目录以及对应的Mapper接口3.7 创建Mapper映射文件3.8 配置mybatis映射文件3.9…

第四十四章 动态规划——背包问题模型(一)

第四十四章 动态规划——背包问题模型(一)一、模型概述二、模型变形1、AcWing 423. 采药(1)问题(2)分析(3)代码2、AcWing 1024. 装箱问题(1)问题(…

ESP8266 ArduinoIDE 物联网web客户端开发

一、使用 esp8266 实现 HTTP 客户端协议 在 arduinoIDE 中,并没有专门的 HTTP 协议客户端库。但是我们可以用 TCP 协议来自动手动实现。 1.1 HTTP 请求报文简介 所谓请求报文,即是基于 TCP/IP 协议发送的一串规范字符,这串规范字符描述了当…

liunx centos9安装nodejs并搭建vue 图文详解手把手教程

首先nodjs的官网找到liunx的安装包 https://nodejs.org/en/download/ 这里不推荐用源码安装,因为实在太慢,我安装时一下在不停安装连续15分钟都还在跑就是不知道什么原因 解压包 tar -xvf /root/node-v18.13.0-linux-x64.tar.xz设置全局 -s后面地址就是…

Android入门第59天-进入MVVM

什么是MVVM用“某大文豪亲”的话说:MVVM并不存在,只是xml里找控件找了太多了,自然而然就“找”出了一套共性。所以,MVVM只是包括了以下这些技术:DataBind;ViewModel双向绑定;Okhttp3retrofitrxj…

图解函数递归、数组详解

目录 一.修炼必备 二.图解递归的执行过程 三.数组 3.1 一维数组 3.2 二维数组 3.3 数组的共同问题 一.修炼必备 1.入门必备:VS2019社区版,下载地址:Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com) 2.趁手武…

视频文缩略图SDK:GleamTech VideoUltimate Crack

Video Reader and Thumbnailer for .NET Core 和 .网络框架 读取地球上的任何视频文件格式。逐帧读取视频文件。生成有意义的缩略图。 VideoUltimate是最快,最简单的.NET视频阅读器和缩略图器,可以读取任何视频文件格式 在地球上。它允许您逐帧读取视频…

C++ 一文解决 数据库ODB 安装/调试/使用的问题

引用: ODB Download (codesynthesis.com) Installing ODB on Linux/UNIX (codesynthesis.com) 缘起: 在开发过程中发现,现有的软件缺乏持久层(Persistence Layer),即专注于实现数据持久化应用领域的某个…

广义零样本学习的转移增量

摘要:零样本学习(ZSL)是一种成功的从未知类中对对象进行分类的范例。然而,它在广义零样本学习(GZSL)设置中遭受严重的性能降级,即以识别来自可见类和不可见类的测试图像。在本文中,为…

C语言-qsort函数基本使用

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C/C】 先来看一下qsort函数的介绍&#xff1a; Compare 函数的返回值描述>0elem1将被排在elem2前面0elem1等于elem2<0elem1 将被…

LeetCode刷题模版:171-174、179

目录 简介171. Excel 表列序号172. 阶乘后的零173. 二叉搜索树迭代器174. 地下城游戏【未理解】179. 最大数结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,…