前后端分离不可忽视的陷阱,深入剖析挑战,分享解决方案,助你顺利实施分离开发。
不管你设计的系统架构是怎么样,最后都是你的组织内的沟通结构胜出。这个观点一直在组织内不断地被证明,但也不断地被忽略。
前后端分离的利与弊
近几年,随着微服务架构风格的引入、前后端生态的快速发展、多端产品化的出现,前后端分离已经成为行业的普遍实践,也是大型企业级分布式架构的缺省选择。
前后端分离也给软件技术人员的职业发展和协作方式带来了新的变化,分别出现了前端工程师、后端工程师、前端开发团队以及后端开发团队。
前后端分离使得前端关注信息架构,处理用户体验相关问题;而后端则关注构建业务能力、数据处理、持久化等问题,并向前端提供API接口(API as product),由前端进行消费。前端工程师不需要关注后端的具体实现和技术框架,后端工程师也不需要关注前端的具体实现和技术框架。
这带来了如下的好处:
- 前后端用户体验和业务逻辑解耦。不同端以及不同用户体验的变化不再影响后端API接口。后端API聚焦在表达业务能力,可同时服务于多端产品,而无需更改。
- 后端无需考虑业务逻辑或能力升级对前端的影响,只要保证接口不变即可。
- 响应变快。对前端尤其是多端服务出现后,前后端分代码和打包部署等技术分离、可以更快地响应不同的用户体验需求,而不必等待后端。
- 前后端工程师能力聚焦,可以专注各自领域的技术学习,聚焦提升自己的专项技能和经验。
- 前后端团队边界明显,认知负荷降低,单点开发效率高,只需关注本端的开发任务和技术即可。
分离带来的好处渐渐体现出来,尤其是在一些大型的互联网项目尤为明显。然而也有很多前后端分离的交付团队中出现了如下的问题:
- 团队开发业务的大小和复杂度随着项目的进行发生变更,引起前后端团队人员比例失调,比如出现前端开发团队进度快,需要等后端团队联调,或者反过来,后端团队等前端的情况,开发进度不畅,沟通协作成本高。
- 这样的临时任务变动,不管新增还是调换人员的动态调整成本高,体验差。
- 业务开发节奏快,没有足够时间量留给后端预先设计API,前端团队只能靠自己的猜测和仅有的共识进行开发,联调时双方分头再改一遍,返工高,沟通协作成本高。
- API的设计也受前端消费者和开发节奏的影响,面向前端的用户体验设计。
- 多个相同组件模块间出现多种不同的做法。
那么,前后端团队不分行不行。当然行,前后端人员不分的协作模式可以灵活匹配开发任务、全栈能力提升、同时团队还可以了解端到端的业务;但同时也使得团队整体的认知负荷高,架构越复杂成本越高,还会影响整体的开发效率。
那到底分不分呢?是什么在影响我们的架构?
组织的沟通结构决定软件构架
康威定律:设计系统的组织由于受到约束,这些设计往往是组织内部沟通结构的副本。
分不分答案其实很简单,就如文章开头所言,不管架构怎么设计,不管作为技术从业者的我们多少次向更好地架构和技术发起努力,但还是会看到“为什么得不到想要的设计,为什么明明是一个架构却各不相同”。因为,在这场对抗中,最后一定是组织的沟通结构胜出。实际上也确实是这样。从上述坏味道以及这些“前后端分离团队”的代码中也可以看出:
- /stock-schema/customer-detail
- /stocks/createAndNext
- /stocks/query-list?
后面就差写上page了
前后端分离看似简单,然而它实际上是技术的分离而非团队的分离。如果要真正实现前后端团队分离的协作模式,或者反过来要想实现前后端技术分离的分布式架构,都要首先考虑组织的沟通结构设计,让它去服务于你想要的及架构。
尤其是当我们在构建和运行大规模软件系统的时候,更需要刻意设计我们的团队沟通结构,以促成“低摩擦”的软件交付,避免“跨部门的职能竖井”、严重依赖外包资源、大量工作件流动受阻、无法提供快速交付或者难以满足现有业务服务的组织反馈机制”。
设计团队的沟通结构
那么,回到最初的问题,如果作为架构师的我们,想要实现前后端技术分离的分布式架构,如何设计团队的沟通结构?
我参考《高效能团队协作模式》中作者给出的四种拓扑类型、三种协作模式,以及设计原则试着给出如下两种答案:
1.方案A - 前后端分离的特性交付团队

图1.1 方案A的端到端交付团队协作模式

图1.2 方案A的端到端交付团队服务的架构图
图1.1和1.2分别展示了方案A中前后端团队如何围绕架构进行协作。方案A的假设在于前后端分别是不同的服务/产品,向不同的服务对象提供某种服务。
每个团队都是端到端的交付团队,好处是团队高度重视用户价值和服务的可用性,可以快速的响应各自的变化,团队的认知边界也很清晰,协作成本低,效率高。它的挑战则在于服务的边界是否定义良好、能否被正确实现,服务提供方可以实施服务管理实践时,这种模式才能正常运作。一旦边界或API不合理,效率会降低。这种方案对团队的服务/产品设计和管理能力要求较高。
方案A中赋能团队、以及可能的领域子系统团队是必不可少的。尤其在团队和业务规模增长的情况下,这两个团队的存在是为了补齐端到端特性团队的能力短板,降低认知负荷,提供特定领域的支持和赋能,同时避免了因组织沟通壁垒导致的规范、实践、重复造轮子、能力缺少等共性问题,尤其促进了跨组织的低摩擦软件交付和特性团队的交付效能。
2 方案B-端到端交付团队

图2.1 方案B的端到端交付团队协作模式

图2.2 方案B的端到端团队协作的架构图
图2.1和2.2分别展示了方案B中前后端团队如何围绕架构进行写作。方案B同样以端到端的特性团队为主,它将整个架构所服务的Web系统看做是一个服务或产品。因此,采取纵向切片的方式划分端到端的特性交付团队。在这样的团队协作中,前后端技术分离但不分家,前后端工程师分别以组件开发的方式进行协作和内部集成。
它的好处在于,能够完成端到端的交付,不需要依赖其它团队,团队自己有能力进行快速的业务创新和探索,也可以与领域子系统进行协作达成目的。
其缺点则在于:
- 前后端开发集成需要较多的协作和沟通成本
- 需要迭代计划的配合
- 这些开发细节和沟通等待会产生较高的认知负荷,对整体效率产生影响
- 对团队能力挑战大
同样,方案B中赋能团队、以及可能的领域子系统团队是必不可少的,这两个团队的存在避免了因组织沟通壁垒导致的规范、实践、重复造轮子、能力缺少等共性问题,尤其促进了跨组织特性团队的低摩擦交付和效能。
然,方案B的另一个问题在于,通常端到端交付的节奏都比较快,要预先留给后端进行设计的时间并不多,所以也会很容易出现在文章开头的问题(又回到原点):
- 前后端并行开发,在集成时返工
- 后端API为前端而设计,耦合度高
- 前后端人员比例与业务的节奏和复杂度不能灵活匹配,出现前端等后端,或者后端等前端联调的情况,造成浪费。
这些问题如何解决?
- 根据业务变化,动态的调整前后端工程师的比例。人员协调成本高,团队人员体验差,成长不利。
- Web开发前后端能力全栈,Story前后端一起做,灵活匹配开发任务、团队能力提升、还可以同时了解端到端的业务和实现;但同时也使得团队整体的认知负荷高,前后端技术和架构越复杂成本越高,还会影响整体的开发效率;也还需要同时考虑人员的成长与发展。
- 适当增加全栈的比例,前端和后端分开做,由全栈同学做“自由人”切换前后端开发任务。自由人越多,团队整体的适应力就越强,对自由人的挑战和依赖较大。
在我的访谈中,1、2、3均有很多团队尝试过或正在采纳。大多数团队前后端的比例在1:2 ~ 1:4之间调整。访谈的同学都提到了两个决策因素:
- 既要尊重现在的前后端技术发展趋势和生态不同,各自有不同的关注点和特点
- 又要为达成业务目标而努力。
那么,还有其它的解法吗?从《高效团队协作模式》一书中我找到了另一种答案:
在考虑这个问题的时候,切入点依然是康威定律的指引。我们会发现,一个项目的架构也并不是一成不变的,它会随着业务的变化而变化,在产品的早期、成熟期、规模期,架构是不同的形态,我们为什么不可以用动态的眼光去设计我们团队的沟通结构呢?答案是显然的。
所以就有如下的解法:

假设业务及技术的复杂度和规模随着时间而增加。那么:
- 在交付初期,业务和技术的复杂度相对较低,要求业务快速上线完成价值转化。
- 前端后端更多的是在构建基础的页面和模型。与此同时,团队刚刚形成,需要端到端的去了解业务的价值,面向Web开发的全栈更容易促成团队的组建、规范和达成业务目标。
- 交付中期,业务开始增长,有复杂的业务流程引入,以及用户体验要求上升。
- 前后端的技术复杂度也随之而来,比如页面的渲染,交互操作,微前端的引入、数据的一致性,业务的可用性都开始有了较高的要求。
同时,代码量也到了一定的量级,在耦合性、内聚性也都出现了不同程度的质量要求。
这个时候,可以适当的开始引入前后端专家,以赋能角色促进的方式与全栈团队进行协作,解决技术难度,整洁代码治理,赋能规范和对应的前后端工程实践等以提高整体的工程效能。 - 交付的成熟期,随着业务规模发展,系统架构也开始变的复杂起来,用户多了起来,除了功能特性,也会在页面加载性能、数据安全等方面提出新的要求。
- 与此同时,也会出现多端产品服务,开发者生态的形成也会促进后端形成平台化的能力。
这些变化都会促成前后端团队的逐渐分离。
这个时候前后端团队也会适当增加转向架构和特定领域的技术专家,可能增加特定领域团队,而大前端的工程师则会补充前端+Bff的开发能力诉求。
总结
前后端分离本质上是技术的分离,而不是人员的分离。团队要不要分取决于你如何设计你的架构,也取决于你的业务模式,所服务的产品形态、团队能力、工程实践的成熟度。
前后端团队分离的成本是极高的,对团队的能力要求也是极高的。它并不适合业务不明确,交付优先级经常变动,需要快速交付,且需要不断创新和探索的业务。
从个人成长来看,前后端分不分并不重要,而是于自己的发展目标与项目机会是否匹配,团队不应该成为我们成长的阻碍,而应该化为促进我们成长的平台。
本文的讨论并不涉及Mobile app的开发。如果你的架构既有Web端,又有Native app, 小程序,你的团队结构是怎么设计的呢?
相关文章:

前后端分离不可忽视的陷阱,深入剖析挑战,分享解决方案,助你顺利实施分离开发。
不管你设计的系统架构是怎么样,最后都是你的组织内的沟通结构胜出。这个观点一直在组织内不断地被证明,但也不断地被忽略。 前后端分离的利与弊 近几年,随着微服务架构风格的引入、前后端生态的快速发展、多端产品化的出现,前后…...

(四)库存超卖案例实战——优化redis分布式锁
前言 在上一节内容中,我们已经实现了使用redis分布式锁解决商品“超卖”的问题,本节内容是对redis分布式锁的优化。在上一节的redis分布式锁中,我们的锁有俩个可以优化的问题。第一,锁需要实现可重入,同一个线程不用重…...

【ROS入门】雷达、摄像头及kinect信息仿真以及显示
文章结构 雷达信息仿真以及显示Gazebo仿真雷达配置雷达传感器信息xacro文件集成启动仿真环境 Rviz显示雷达数据 摄像头信息仿真以及显示Gazebo仿真摄像头新建xacro文件,配置摄像头传感器信息xacro文件集成启动仿真环境 Rviz显示摄像头数据 kinect信息仿真以及显示Ga…...

实用篇-认识微服务
一、服务架构演变 1. 单体架构 单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署 单体架构的优点: 架构简单部署成本低 单体架构的缺点: 耦合度高 2. 分布式架构 分布式架构: 根据业务功能对系…...

【产品运营】产品需求应该如何管理
产品项目在进行时经常会有一些需求需要实现,需求是产品更新迭代的动力,需求也是从用户诉求转化而来;在做需求管理时,我们需要判断一个需求的优先级等方面,对产品进行优化; 目录: 一、 为什么要…...

Linux 系统调用IO口,利用光标偏移实现文件复制
用系统调用IO函数实现从一个文件读取最后2KB数据并复制到另一个文件中,源文件以只读方式打开,目标文件以只写的方式打开,若目标文件不存在,可以创建并设置初始值为0664,写出相应代码,要对出错情况有一定的处…...

【原创】指针变量作为函数参数要点注意
指针变量作为函数参数要点注意(已写至笔记) 1传参指针不加*(main中函数) 2收参指针要加*(被main调用的函数) 3传参指针名可与收参指针名不同,不影响 4【问】如何看主函数中指针所指内容是否改变…...

SpringMVC Day 04 : 数据绑定
前言 SpringMVC是一个非常流行的Java Web框架,它提供了很多方便的功能和工具来帮助我们构建高效、灵活的Web应用程序。其中,数据绑定就是SpringMVC中非常重要的一部分,它可以帮助我们方便地将请求参数绑定到Java对象上,从而简化了…...

2.3.1 协程设计原理与汇编实现
1.为什么要有协程? 同步的编程方式,异步的性能。同步编程时,我们需要等待io就绪。但是在协程这里,我们使用一种机制,当io需要等待时,就切到下一个io,之后当之前的io就绪时,再切换回来…...

J2EE项目部署与发布(Windows版本)->会议OA单体项目Windows部署,spa前后端分离项目Windows部署
会议OA单体项目Windows部署spa前后端分离项目Windows部署 1.会议OA单体项目Windows部署(以实施的角度) 将项目放入webapp,项目能够访问: 首先拿到war包和数据库脚本,并检查是否有什么问题。 如何查看项目报错信息(当你…...

Lua脚本语言
1. 概念 Lua(发音为"loo-ah",葡萄牙语中的"lua"意为月亮)是一种轻量级的、高效的、可嵌入的脚本编程语言。官网Lua最初由巴西计算机科学家Roberto Ierusalimschy、Waldemar Celes和Luiz Henrique de Figueiredo于1993年开…...

cat()函数和print()函数的区别
目录 区别一: 区别二: cat、print函数都是输出函数。 区别一: cat()函数不能赋值; print()函数可以赋值。 x<-cat("hello world") //赋值 hello world x //cat函数无返回值 NULLy<-print("hello …...

宝塔面板安装Python和Flask(新版Python项目)
(一)宝塔面板的项目菜单,打开Python项目的“项目版本管理” 安装Python版本3.10.0。 会创建一个Python版本的文件夹www/server/pyproject_evn/versions/ 会创建一个Python虚拟环境的文件夹www/server/pyproject_evn/python_venv/ …...

火柴排队.
题意:给两列火柴,可以交换任意相邻的火柴,使得(ai-bi)^2的和最小,求最小交换次数。 分析:使得(ai-bi)^2的和最小,即a^2-2abb^2的和最小,那么使得2ab最大,就可…...

改善游戏体验:数据分析与可视化的威力
当今,电子游戏已经超越了娱乐,成为一种文化现象,汇聚了全球数十亿的玩家。游戏制作公司正采用越来越复杂的技术来提高游戏质量,同时游戏数据分析和可视化工具变得不可或缺。 数据的力量:解析游戏体验 游戏制作涉及到大…...

GEE:本地影像上传到GEE的Assets中,并输入机器学习算法中作为特征变量
作者:CSDN @ _养乐多_ 当我们在 Google Earth Engine(GEE)中应用机器学习算法时,会输入一些影像作为特征变量数据,进一步根据这些特征影像去推理未知区域的数据。但是 GEE 平台上计算特征变量的 API 函数并不是非常全面,我们希望获得更多的特征用于分类。这个时候,我们…...

【Mybatis源码】XMLConfigBuilder构建器 - 读取XML配置初始化Configuration对象
XMLConfigBuilder是Mybatis中定义的进行构建Configuration对象的类,此类用于读取XML配置文件创建并初始化Configuration对象; 上一篇中我们介绍了XMLConfigBuilder构建器加载XML配置文件以及创建Configuration对象https://blog.csdn.net/m1729339749/article/details/133983…...

Python算法练习 10.28
leetcode 700 二叉搜索树中的搜索 给定二叉搜索树(BST)的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。 示例 1: 输入:root [4,2,7,1,…...

【java学习—八】单例设计模式(5)
文章目录 1. 相关概念2. 单例设计模式-饿汉式3. 单例设计模式-懒汉式4. 总结 1. 相关概念 单例:只有一个实例(实例化对象) 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的…...

【设计模式】第4节:创建型模式之“单例模式”
一、介绍 采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。 不使用单例模式的UML类图: 使用单例模式的UML类图: 使用场景: 需要频繁创建或销毁的对象…...

NodeJS爬取墨刀上的设计图片
背景 设计人员分享了一个墨刀的原型图,但是给的是只读权限,无法下载其中的素材;开发时想下载里面的一张动图,通过浏览器的F12工具在页面结构找到了图片地址。 但是浏览器直接访问后发现没权限: Nginx 的 403 页面。。…...

linux--
一、crond 任务调度 1、原理示意图 2、crontab 进行定时任务的设置 2.1. 概述 任务调度,是指系统在某个时间执行的特定的命令或程序。任务调度分类: 系统工作: 有些重要的工作必须周而复始地执行。如病毒扫描等 个别用户工作:个别用户可能希望执行某些…...

conda虚拟环境笔记收录
1、安装conda 增加执行权限: chmod x Anaconda3-2023.03-1-Linux-x86_64.sh 开始执行:./Anaconda3-2023.03-1-Linux-x86_64.sh2、查看版本 conda --version3、查看当前虚拟环境 虚拟环境和全局环境有前缀可见 如果不进行设置,重新启动就变成…...

RGB-T Salient Object Detection via Fusing Multi-Level CNN Features
ADFC means ‘adjacent-depth feature combination’,MGF means ‘multi-branch group fusion’,JCSA means ‘joint channel-spatial attention’,JABMP means ‘joint attention guided bi-directional message passing’ 作者未提供代…...

安卓开发实例:方向传感器
调用手机的方向传感器,X轴,Y轴,Z轴的数值 activity_sensor.xml <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayoutxmlns:android"http://schemas.android.c…...

[论文笔记]GTE
引言 今天带来今年的一篇文本嵌入论文GTE, 中文题目是 多阶段对比学习的通用文本嵌入。 作者提出了GTE,一个使用对阶段对比学习的通用文本嵌入。使用对比学习在多个来源的混合数据集上训练了一个统一的文本嵌入模型,通过在无监督预训练阶段和有监督微调阶段显著增加训练数…...

Prometheus字段解析
官方文档::Configuration | Prometheus 1、全局配置指定在所有其他配置上下文中有效的参数。它们还用作其他配置部分的默认值。 global:# How frequently to scrape targets by default.默认情况下,定期抓取目标的频率是多久一次?…...

msigdbr hallmarks gsea broad研究所
使用msigdbr r包 #BiocManager::install("msigdb") #https://www.gsea-msigdb.org/gsea/msigdb #https://cran.r-project.org/web/packages/msigdbr/vignettes/msigdbr-intro.html #https://bioconductor.org/packages/release/data/experiment/vignettes/msigdb/ins…...

理解V3中的proxy和reflect
现有如下面试题 结合GeexCode和Gpt // 这个函数名为onWatch,接受三个参数obj、setBind和getlogger。 // obj是需要进行监视的对象。 // setBind是一个回调函数,用于在设置属性时进行绑定操作。 // getlogger是一个回调函数,用于在获取属性时…...

实现寄生组合继承
寄生组合继承是一种继承方式,它通过组合使用构造函数继承和原型继承的方式,实现了高效而且正确的继承方式。 具体实现步骤如下: ① 定义一个父类,实现其属性和方法: function Person(name) {this.name namethis.age…...