单体分层应用架构剖析

news2025/1/19 17:04:40

分层单体架构风格是分层思想在单体架构中的应用,其关注于技术视角的职责分层。同时,基于不同层变化速率的不同,在一定程度上控制变化在系统内的传播,有助于提升系统的稳定性。但这种技术视角而非业务视角的关注点隔离,导致了问题域与工程实现之间的Gap,这种割裂会导致系统认知复杂度的提升。

作者:倪新明

1 经典单体分层架构

1.1 四层单体架构风格

经典的四层单体分层架构如下图所示,应用在逻辑上划分为展现层、业务层、持久层及数据存储层,每层的职责如下:

展现层:负责给最终用户展现信息,并接受用户的输入触发系统的业务逻辑。用户可以是使用系统的人,也可以是其他软件系统。

业务层:关注系统业务逻辑的实现

持久层:负责数据的存取

数据存储层:底层的数据存储设施

这种分层单体架构可能是大多数开发人员最早接触、最为熟悉的应用架构风格,其特点是:

• 层间的依赖关系由上到下逐层向下直接依赖,每层都是关闭状态,请求的数据流向从上到下,必须严格通过每个分层进行流转,而不能进行穿透调用。

• 关注点隔离:通过分层将系统的关注点进行垂直分配,每层只关注自身层边界内的职责,层间职责相互独立不存在交叉。比如业务层负责处理系统的核心业务逻辑,而持久层则关注于对数据的存取。

除了关注点隔离这一维度,分层也在 “变化” 的维度进行隔离。每层的变化速率不同,由下级上逐层增加,展现层的变化速率最快,数据存储层变化速率最低。通过严格层依赖关系约束,尽量降低低层变化对上层的影响。这个特点的上下文是分层之间依赖于抽象,而非依赖于具体。当实现发生变化而接口契约不变时,变更范围框定在当前层。但,如果是接口契约的变更,则可能会直接影响到上游的依赖层。

这种分层架构风格具有明显的优势:

• 分层模型比较简单,理解和实现成本低

• 开放人员接受度和熟悉程度高,认知和学习成本低

1.2 五层单体架构风格

四层架构面临的问题是:

层间数据效率问题: 由于层间调用关系的依赖约束,层间的数据流转需要付出额外成本

• 业务层服务能力的复用性:业务层中处于对等地位的组件或模块之间存在共享服务的诉求

从复用性的角度考虑,如下所示的五层架构中,通过引入中间层解决复用问题。将共享服务从业务层沉淀到通用服务层,以提高复用性。其特点是:

• 引入通用服务层提供通用服务,提高复用性

• 通用服务层是开放层,允许调用链路穿透,业务层可以按需直接访问更下层的持久层

相比于四层架构,五层分层架构的主要优势是:通过中间层的引入一定程度解决系统的复用性问题。但从反向角度看,正是由于中间层的引入导致了如下问题:

• 引入中间层降低了数据传输效率,提高了开发实现成本

• 有造成系统混乱度提升的风险:由于通用服务层的开放性导致业务层可以穿透调用。但这种是否需要进行穿透的场景无法形成统一的判定原则,往往依赖于实现人员的个人经验进行权衡,同一个业务场景由不同的开发人员实现可能会有不同的判定结果(在四层架构中如果放开层间调用约束也会存在该问题)。随着业务需求迭代,系统的依赖关系一定会日趋增加,最终形成复杂的调用关系,也导致系统复杂性上升,增加团队成员的认知成本。

2 单体分层架构的共性问题探讨

当然,正是由于其极高的接受度,也造成了大家对分层的认知误区,认为分层是必然的“默认选项” ,从而忽略了分层的本质。分层到底是为了解决什么问题?

分层本质上是处理复杂性的一种方式:将复杂性在不同级别进行抽象,通过分层进行职责隔离,以此降低认知成本。同时,通过分层形成的“屏障”,控制变化在系统间的传播,提升系统稳定性。

不论是四层架构还是五层架构都是分层思想在单体应用架构风格下的实践,这种分层模式存在的固有问题主要体现在以下几个方面:

• 分层对系统复杂度和效率的影响

• 变化真的能完全隔离吗?

• 问题域与解决方案的隔离

2.1 分层对系统复杂度和效率的影响

如上文所述,分层架构中各层的变化速度不同。越往上变化越快,稳定性越低,越往下变化越慢,稳定性越高。比如,展现层的用户展示逻辑可能频繁变化,对应于不同的场景诉求展示数据及形式都可能不同。

如果划分层次越多,层间依赖关系越严格,则系统的调用链路和依赖关系会更加清晰。但,请求及响应的链路越长,层间数据转换有额外成本。即使引入各种数据转换工具,比如MapStruct,实现起来依然会感觉非常繁琐和重复。

如果划分层次越多,层间依赖关系宽松,允许跨层调用(如下所示的从展现层调用持久层只是一个示意),则能在一定程度降低数据频繁转换的成本。但:

• 其一:如何判定是否要跨层调用很难形成统一的严格判定标准,只能进行粗粒度划分。因此,在实现过程中会有不同的判定结果,系统的调用关系会随着代码规模增长而日趋复杂。当然,团队可以加强代码评审的粒度,每次评审基于是否穿透调用进行讨论、判断并达成一致。但实际经验是,由于人为因素,靠严格的代码评审并不能保证决策的一致性。

• 其二:如果允许跨层调用,则意味着 “模型” 的穿透,低层的模型会直接暴露在更上层,这与我们追求的组件内聚性和模型的封装性存在冲突

:层间的依赖约束是一种架构决策,可以考虑通过自动化单元测试机制进行保证,具体参考

《 基于ArchUnit守护系统架构 》

《 轻量级的架构决策记录机制 - ADR》

2.2 变化的隔离

我们对分层有一个普遍的、“先入为主” 的认知,分层能够隔离变化。首先会想到的例子,比如,如果底层的数据库发生了变更,又或者ORM框架发生了变更,那么,我们只需要修改DAO层的实现,而不需要更改上层的业务层代码。

• 你真的会替换数据库吗?你真的会替换ORM框架吗?有可能,但概率非常低,大部分系统并不会发生这种场景。

• 发生替换就真的能隔离吗?如果你的层间不是依赖于抽象,而是依赖于具体,那么隔离也无从谈起。

• 即使层间依赖于抽象,变化就真的隔离了吗?实现发生变化的直接结果就是依赖方需要引用新的实现,这种变化也同样会影响到上层。只不过是这种变化可能交由IOC容器了

但,这个是变化隔离的全部吗

• 如果是展现层需要增加一个新的字段,而当前数据库模型中没有?

• 如果是数据库中需要增加一个新的字段,而展现层和业务逻辑层不关心?

• 如果是…

所以,引起系统变化的原因很多,场景各异,业务诉求亦不相同,分层对变化隔离程度也不相同:

分层可以控制变化在系统内的传播,由于变化场景的多样化,分层不能完全的隔离变化。

2.3 问题域与解决方案的割裂

重新思考下上文提到的分层单体架构的特点之一:关注点隔离,展现层、业务层、数据访问层、存储层等各层聚焦于自身的职责。这种关注点的本质是什么?

技术视角的隔离!!!

每层都是从技术视角将技术关注点进行隔离,而非业务领域视角。技术视角是研发友好的,作为开发人员,天然的可以理解和接受这种技术维度的统一语言:DAO层只负责处理数据相关逻辑,Controller层之服务处理Restful API相关,RPC层只处理与外部系统的跨进程调用等等。

而对于非常核心的业务概念,比如以订单为例,在单体分层架构下需要回答这样一个问题:“订单组件” 在哪里?

在经典的分层单体架构风格中,典型的实现如下图所示:

• OrderConroller:Spring技术栈下的系统访问的Rest接口

• OrderService/OrderServiceImpl:订单的核心业务逻辑实现服务,实现诸如下单、取消订单等逻辑

• OrderDAO/OrdeDAOImpl:订单数据的存取

订单组件并不是以一个单一的、内聚的事物存在,其组成元素OrderService以及其依赖的OrderDAO分散于不同的层,因此,这种模式下订单组件只是逻辑性、概念性的存在。作为业务域的核心抽象,订单组件没有真实的、直观的、内聚的反映在代码实现中。我们在工程代码库中寻找“订单组件”:

• 首先,在工程顶层最先看到的是技术视角的Module(Maven Module):web、service 、dao

• 然后,需要在各层导航才能一窥其全貌

在IDE的支持下,这种导航并不会很复杂。但问题的根本在于:认知成本的增加**。**

我们去了解系统,天然的是从业务域而非技术域出发,单体分层恰恰是从技术域而非业务域出发,这种不同导致业务域与实现间的割裂,增加了对系统的认知成本**。**

实现要反应抽象,组件化思维本质上一种模块化思维,通过内聚性和封装性,将问题空间进行拆分成子空间,分而治之。对外通过接口提供组件能力,屏蔽内部的复杂性。接口契约的大小粒度需要权衡,粒度越小,能力提供越约聚焦,理解和接入成本越低,但通用性越差。接口契约粒度越大,则通用性越强,但理解和接入复杂性越高。

将组件化思维应用于单体分层架构,引申出模块化单体架构风格。应用架构按照问题域进行模块化组织,而非基于技术关注点进行拆分。组件内部遵循内聚性原则,其内包含了实现组件能力所需要的各个元素及交互关系。组件之间通过统一的、合适粒度的接口契约进行交互,不直接依赖于组件的内部能力或模型。同时,组织良好的模块化单体应用架构也是进行微服务拆分的重要保证。如果你无法在单体架构中进行优雅的模块化组织,又何谈合理的微服务拆分呢?

3 结语

单体分层架构风格是分层思想在单体架构中的应用,其关注于技术视角的职责分层。同时,基于不同层变化速率的不同,在一定程度上控制变化在系统内的传播,有助于提升系统的稳定性。但这种技术视角而非业务视角的关注点隔离,导致了问题域与工程实现之间的Gap,这种割裂会导致系统认知复杂度的提升。将组件化思维应用于单体分层架构,模块化单体技术视角的分层拉回至业务域视角的模块化,一定程度上降低业务与工程实现间的隔离。良好的模块化是单体走向微服务的重要基石,如果模块化设计较差的系统,不仅会增加微服务拆分的成本,更为重要的是,会增加形成分布式单体的概率和风险。

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

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

相关文章

Unity-2D游戏-打击感与敌人AI

前言 最近快搞毕设了,学一些Unity2D游戏开发的知识,发现b站宝藏up主奥飒姆Awesome的两个蛮不错的教程,我想简单记录一下它这个游戏设计的方法。 我不一点点实现了,就是分析一下大致框架(方便以后套用) 资…

广东电子MES系统在电子厂实施的功能和流程

1、电子行业特点电子行业为典型的离散性加工企业,其管理核心的问题在于SN号的管理和物料追溯,即产品、半成品、关键部件都有SN号,且需要实现物料追溯。2、电子行业MES解决方案针对行业需求,我们提供了如下的解决方案:采…

【Python模块】日期时间

在平时开发工作中,我们经常需要用到日期时间,比如日志记录、日期时间的计算、时间字段的赋值等。Python 提供了 time 模块、datatime 模块及子模块、calendar 模块等内置模块,可实现对日期时间的设置、获取、转换等常见操作。 一、日期时间的…

基于STM32G431嵌入式学习笔记——七、定时器定时

一、题目引入 上述为第13届蓝桥杯省赛节选内容,为了研究定时器的机理并独立书写计时函数,上述内容简化为以下要求: ①按下B4按键,LD1点亮5s后熄灭 ②按下B3按键,LD2以0.1秒为间隔切换亮灭状态 二、基础知识 定时器相…

请问各位程序员,是我的思维方式有错误吗?

你好呀,我是歪歪。 前几天知乎给我推送了一个问题,我点进去一看,好家伙,竟然把我血压都看上来了是怎么回事。 我先把问题复述一遍,聊天记录比较长,但是看的过程中容易冲动,注意控制情绪&#…

麦芽糖-聚乙二醇-人血清白蛋白,HAS-PEG-maltose

中文名称:麦芽糖-人血清白蛋白 英文名称:maltose-HAS 别称:人血清白蛋白修饰麦芽糖,HAS-麦芽糖 PEG接枝修饰麦芽糖 麦芽糖-聚乙二醇-人血清白蛋白 HAS-PEG-maltose 人血清白蛋白-PEG-麦芽糖 纯度:95% 存储条件…

物料搬运装置及控制系统设计(CAD+PLC)

目 录 1 绪论 1 1.1 物料搬运装置的概述 1 1.2 物料搬运装置的发展史 1 1.3 气动技术及气动物料搬运装置的发展过程 2 1.4 物料搬运装置未来的发展趋势 3 1.5 本课题研究内容 6 1.6 课题研究的意义 6 2 物料搬运装置的总体设计方案 7 2.1 物料搬运装置的工作原理及系统组成 7 2…

动态规划 DP专题

跟着ygg的dp题单刷的dp 1.代码源每日一题 Div1 连续子序列 分析&#xff1a; dp数组开成map&#xff0c;则状态转移式dp[i] max(dp[i - 1] 1, dp[i]) AC代码&#xff1a; #include <bits/stdc.h>using namespace std; typedef long long ll; #define int ll #define …

Day814.电商系统表设计优化案例分析 -Java 性能调优实战

电商系统表设计优化案例分析 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于电商系统表设计优化案例分析。 如果在业务架构设计初期&#xff0c;表结构没有设计好&#xff0c;那么后期随着业务以及数据量的增多&#xff0c;系统就很容易出现瓶颈。 如果表结构扩展…

CentOS7.9 安装配置MySQL5.7.30

1.下载mysql安装包&#xff1a;mysql-5.7.30-linux-glibc2.12-x86_64.tar.gz 2. 卸载自带的mariadb和mysql 检查是否安装了mariadb和mysql&#xff0c;有时候默认安装了 rpm -qa | grep mariadb rpm -qa | grep mysql 如果没有&#xff0c;就可以安装mysql&#xff0c;如果有&a…

Centos7搭建SVN代码控制服务器

Centos7搭建SVN代码控制服务器检查SVN是否安装创建SVN版本库配置代码库设置允许访问远程仓库的用户帐号密码设置权限控制设置SVN服务配置启动svn与停止启动SVN关闭SVN访问拉取远程仓库代码检查SVN是否安装 1、centos7系统自带SVN rpm -qa subversion2、如果没有则通过yum安装 …

[论文精读|博士论文]面向文本数据的关系抽取关键技术研究

电子科技大学 2022.3.15博士论文 实体关系的方向性语义缺失&#xff0c;使得关系的判别缺乏对文本蕴含语义特征的利用 提出——基于句法关系的方向敏感型句子级关系抽取算法 利用依存句法树结构信息构建双向依存路径结构&#xff08;新的文本策略解决过度剪裁&#xff09;额外…

构建直接序列扩频系统模型(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

【分析笔记】全志 i2c-sunxi.c 控制器驱动分析

分析平台&#xff1a;全志 A64 内核版本&#xff1a;Linux 4.9 数据手册&#xff1a;Allwinner_A64_User_Manual_V1.1.pdf (whycan.com) 驱动框架 I2C 设备驱动 作为方案应用来说&#xff0c;我们是最经常要动的地方&#xff0c;这一层主要与具体的芯片功能强关联&#xff0c…

RK3588平台开发系列讲解(USB篇)内核 USB 配置

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、USB PHY CONFIG二、USB Host CONFIG三、USB OTG CONFIG四、USB Gadget CONFIG沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍USB的相关配置。 USB 模块的配置及保存和其它内核模块的配置方法一…

英语考试的作文模板

考试需要&#xff0c;但是老是忘记&#xff0c;所以发出来备忘~~~~~ 这里写目录标题1 高频考点&#xff1a;正面话题 (能力/事情/习惯/行为/品质/意识/习惯&#xff09;1.1 题干关键词与结构1.2 开头段1.3 主体段2 一起写范文【正面话题】主体段&#xff1a;重要性怎么做重要性…

【AI学习笔记】jupyter notebook 默认路径修改(超简介,超详细)

文章目录修改前&#xff1a;修改notebook默认路径&#xff1a;1. 找到 Anaconda 的安装目录2. 修改 notebook 安装位置3. 删除"%USERPROFILE%/"内容修改后&#xff1a;【声明&#xff1a;由于我的电脑有 Anaconda3的root环境 和 名为TensorFlow 的 Anaconda虚拟环境&…

操作系统学习笔记(Ⅱ):进程

目录 1 进程 1.1 定义、组成、组织方式与特征 1.定义 2.组成 ​3.组织方式 4.进程的特征 1.2 进程的状态与转换 1.状态 2.进程状态的转换 1.3 进程控制 1.基本概念 2.进程控制相关的原语 1.4 进程通信 1.共享存储 2.消息传递 3.管道通信 1.5 线程概念和多线…

Python数据分析实战-实现模型K折交叉验证(附源码和实现效果)

前面我介绍了可视化的一些方法以及机器学习在预测方面的应用&#xff0c;分为分类问题&#xff08;预测值是离散型&#xff09;和回归问题&#xff08;预测值是连续型&#xff09;&#xff08;具体见之前的文章&#xff09;。 从本期开始&#xff0c;我将做一个数据分析类实战…

mybatisplus savebatch 多数据源时候,sqlSessionFactory 不正确踩坑记录。

记录一下 mybatis-plus sharding-JDBC 的时候&#xff0c;因为配置多数据源和多个SqlSessionFactory导致 mybatisPlus 执行 saveBatch 异常的问题。 具体异常就是 saveBatch 执行的数据源&#xff0c;与期望的不一致。其实是因为 SqlSessionFactory 错误导致的。 项目中有2个…