Monorepo or 物料市场?结合工作实际情况对公司现有前端体系的思考
前言
去年年中基于若依vue前端框架进行了改造,加上后端的配合,我写了一套脚手架和项目中后台模板。中后台模板中包含了许多基础代码,比如登录/注册、路由、权限等等相关功能。这个中后台模板是基于我们实际开发定制的,所以跟通用的中后台模板(vue-element-admin)还不一样,可以认为是快速搭建系统的一种解决方案。
问题
在搭建了多个系统之后,我们遇到了点问题。项目模板在前期并不是特定稳定,有些功能需要进行调整,如果我们需要调整则需要在所有项目都进行手动调整,这会变得非常麻烦。随着系统的不断增加,这种每个系统都需要手动调整的方案势必会造成大量的资源浪费。所以我们开始讨论如何解决这个问题。
系统架构
在提出正式解决方案之前,我先简单介绍一下我们系统架构思路。我们前后端项目都是基于若依系统进行改造的,后端是使用Spirng cloud Gateway 将 api 分发到各个项目的微服务中,前端则是通过设置请求头来告知当前系统访问的项目。

方案一 – Monorepo
Monorepo – 单体仓库基建方案。将多个开发项目放到一个项目中进行管理的一种手段。这种方案的优势和劣势也是显而易见的:
优势
- 代码重用将变得非常容易:由于所有的项目代码都集中于一个代码仓库,我们将很容易抽离出各个项目共用的业务组件或工具,并通过 TypeScript,Lerna 或其他工具进行代码内引用;
- 依赖管理将变得非常简单:同理,由于项目之间的引用路径内化在同一个仓库之中,我们很容易追踪当某个项目的代码修改后,会影响到其他哪些项目。通过使用一些工具,我们将很容易地做到版本依赖管理和版本号自动升级;
- 代码重构将变得非常便捷:想想究竟是什么在阻止您进行代码重构,很多时候,原因来自于「不确定性」,您不确定对某个项目的修改是否对于其他项目而言是「致命的」,出于对未知的恐惧,您会倾向于不重构代码,这将导致整个项目代码的腐烂度会以惊人的速度增长。而在 monorepo 策略的指导下,您能够明确知道您的代码的影响范围,并且能够对被影响的项目可以进行统一的测试,这会鼓励您不断优化代码;
- 它倡导了一种开放,透明,共享的组织文化,这有利于开发者成长,代码质量的提升:在 monorepo 策略下,每个开发者都被鼓励去查看,修改他人的代码(只要有必要),同时,也会激起开发者维护代码,和编写单元测试的责任心(毕竟朋友来访之前,我们从不介意自己的房子究竟有多乱),这将会形成一种良性的技术氛围,从而保障整个组织的代码质量。
劣势
- 项目粒度的权限管理变得非常复杂:无论是 Git 还是其他 VCS 系统,在支持 monorepo 策略中项目粒度的权限管理上都没有令人满意的方案,这意味着 A 部门的 a 项目若是不想被 B 部门的开发者看到就很难了。(好在我们可以将 monorepo 策略实践在「项目级」这个层次上,这才是我们这篇文章的主题,我们后面会再次明确它);
- 新员工的学习成本变高:不同于一个项目一个代码仓库这种模式下,组织新人只要熟悉特定代码仓库下的代码逻辑,在 monorepo 策略下,新人可能不得不花更多精力来理清各个代码仓库之间的相互逻辑,当然这个成本可以通过新人文档的方式来解决,但维护文档的新鲜又需要消耗额外的人力;
- 对于公司级别的 monorepo 策略而言,需要专门的 VFS 系统,自动重构工具的支持:设想一下 Google 这样的企业是如何将十亿行的代码存储在一个仓库之中的?开发人员每次拉取代码需要等待多久?各个项目代码之间又如何实现权限管理,敏捷发布?任何简单的策略乘以足够的规模量级都会产生一个奇迹(不管是好是坏),对于中小企业而言,如果没有像 Google,Facebook 这样雄厚的人力资源,把所有项目代码放在同一个仓库里这个美好的愿望就只能是个空中楼阁。
从上述引用来看,monorepo的劣势对于我们小团队太过于沉重了。第一,我们没有足够的人力资源,这个问题还不单单在于项目权限的控制,更在与我们团队的人员水平和精力 – 是的,不得不承认团队水平参差不齐,并非所有人都愿意去付诸精力和汗水去学习。第二,我们已有的需求并不一定适合这种解决方式。我们的目的是快速搭建各个系统,前端并不需要微服务化,各个仓库之间没有明确调用关系。第三,对于现有系统的改造会很困难。我们每个系统都要重复的功能页面,但是我们并不能保证所有的前端页面都是一样的表现形式 – 可能针对某个特殊项目有功能调整。基于这一点我们如果要使用Monorepo的进行代码调整的话,我们需要将基础框架与页面、组件进行分离。这个工作量相当庞大,我们希望有更加轻量级的解决方案。
方案二 – 插件开发模式
插件开发模式 – 将系统分成shell和runtime模块,shell视为宿主,runtime视为插件。每个项目都包含shell和runtime两个部分,其中shell是包含基础的功能,包含开发、打包和基础的框架内容,runtime包含了业务相关模块。shell通过读取每个项目的配置文件进行调整,shell运行时会加载runtime的相关业务模块。
优势
这种开发方式好处是可以将 基础框架 与 业务开发 进行分离,如果后续我们需要升级shell 时就会变得相对简单(如果业务本身不需要修改shell的话),同时分离还降低了系统开发的复杂程度,使常规开发人员只关注业务本身的开发就好了。
劣势
- 完全剥离框架与业务困难:目前的项目模板就是根据业务系统定制的,系统模板本身就集成了部分业务模块。但是我们并不能保证已经集成的业务模块不会发生变动或者需要新增一些新的公共业务模块。剥离基础框架和业务并不困难,困难的是我们的框架本身包含了公共的业务模块,我们无法对这块儿进行较好的处理。
- 短期工作量大,难以渐进完成:框架杂糅了业务模块,如果需要采取插件模式开发,对现有的调整框架的调整较大。 一是要划分清楚框架与业务代码,对shell和runtime进行分离;二是要编写配置模块,明确需要读取哪些配置文件;三是已有的每个系统都需要按照这种较大的改动的方式去做,工作量大。
这确实是一种思路,但是我认为它适用的场景是那种 shell 与 runtime 分离比较容易的情况,类似于APP或者是小程序开发 – 有一个统一的shell运行各个需要加载的模块,降低不同开发人员使用不同的shell导致多个项目合并打包失败的问题。
方案三
物料市场。
物料即组成一个前端项目的不同单位,根据抽象粒度的不同,我们将物料从小到大分为 组件(component)、区块(block)和模板(template) 。在基于物料体系的开发中,我们使用模板物料来初始化前端工程,提供最佳实践,解决工程问题,再使用区块和组件像搭积木一样快速搭建页面。
物料分为 组件(component)、区块(block)和模板(template) 三种类型:
- 组件(component):组件是组成页面的基本结构单元,是对局部交互逻辑的抽象和封装。通常需要设计和暴露属性、插槽、事件和方法等 API。使用者根据这些 API 直接使用,一般不需要做二次修改。
从业务维度去看,组件又可分为基础组件和业务组件两种:
- 基础组件:与业务无关的组件,基础组件保持统一的视觉规范,考虑高内聚低耦合的设计思想,例如 UButton、UInput、UTableView 等,在 Vusion 体系,官方维护 Cloud UI 基础组件库;
- 业务组件:面向业务的组件,一般功能比较确定可复用,同时复杂度较高,例如用户选择器、计费卡片等。
- 区块(block):区块可以理解为在页面中,由一些组件组合而成的代码片段。在一个页面中,使用者可以快速把某个区块的代码添加到自身项目里,进行二次修改。
- 模板(template):项目的样板工程,包含了完整前端项目所有组成部分,包括布局、常用页面、基础插件、工程配置等,用户可以快速初始化项目。
我们已有中后台框架模板,就是对应到了物料中的模板这块儿。对于我个人而言我更加倾向于采取这种策略,原因如下:
- 改造中后台框架调整工作量可控的。根据我们对物料的抽象程度不同,我们可以对项目进行不颗粒度划分。举例说明,我们将登录页面及其相关功能做成区块,那么新的项目就可以直接添加到进去,并根据业务要求进行修改。如果是需要调整,在将区块下载下来后,能做到快速更新的能力。
其他相关优点:
- 最大化资源复用。项目,团队,成员之间轻松共享。
- 提升迭代上下游协作效率。
- 提升人效比。提升项目中前端开发人员的人效比,让前端做的更快、更多、更好;
- 能力中台化。支撑业务快速发展。
参考链接:
All in one:项目级 monorepo 策略最佳实践
什么是物料
物料前端中台建设
从生产到消费,设计基于物料的前端开发链路
从业务组件库看前端物料生态
如何建设前端物料平台?
相关文章:
Monorepo or 物料市场?结合工作实际情况对公司现有前端体系的思考
前言 去年年中基于若依vue前端框架进行了改造,加上后端的配合,我写了一套脚手架和项目中后台模板。中后台模板中包含了许多基础代码,比如登录/注册、路由、权限等等相关功能。这个中后台模板是基于我们实际开发定制的,所以跟通用…...
GEE学习笔记八十八:在自己的APP中使用绘制矢量(上)
在GEE中尤其是自己的APP中调用绘制的矢量图形方法之前没有合适的方法,但是现在可以通过ui.Map.DrawingTools(...)以及ui.Map.GeometryLayer(...)结合来做。具体的API如下图: 在这一篇中我先通过一个简单的例子来展示一下使用这些API后可以实现什么效果&a…...
“笨办法”学Python 3 ——练习 39. 字典,可爱的字典
练习39 源代码 # create a mapping of state to abbreviation #创建一个州与缩写的映射 states {Oregon:OR,Florida:FL,California: CA, New York: NY,Michigan:MI} #创建一个字典,key为州名,value为州缩写#Create a basic set of states and some cit…...
模糊的照片如何修复清晰?
相信有很多人用手机拍照时,觉得拍出来的照片一定是很漂亮的,结果拍了之后,拿出来一看模糊一片,根本看不清是什么。或者是只显示一半另一半模糊一片。而这些精彩瞬间很多时候是无法重拍的。虽然谁也不想拍出的照片出现模糊…...
如何理解session、cookie、token的区别与联系?
session、cookie、token。 相信学过接口的朋友都特别熟悉了。 但是对我一个刚接触接口测试的小白来说,属实有点分不清楚。 下文就是我通过查阅各种资料总结出来的一点理解,不准确的地方还请各位指正。 (文末送洗浴中心流程指南)…...
【MyBatis】| MyBatis分页插件PageHelper
目录 一:MyBatis使⽤PageHelper 1. limit分⻚ 2. PageHelper插件 一:MyBatis使⽤PageHelper 1. limit分⻚ (1)概念: ①页码:pageNum(用户会发送请求,携带页码pageNum给服务器&am…...
Java枚举类详解
一、定义格式 public enum s { 枚举项1,枚举项2,枚举项3; } // 定义一个枚举类,用来表示春,夏,秋,冬这四个固定值 public enum Season {SPRING,SUMMER,AUTUMN,WINTER; } 二、枚举的特点 1、所有枚举类都是Enum的子类 2、我们可以通…...
C语言数组
一.数组定义 数组由数据类型相同的一系列元素组成 如 int main(){ float candy[365]; char code[12]; int states[50]; … } cnady是包含了365个float元素的数组。code是包含了12个char类型的数组。states包含了50个int类型的数组。 二.数组初始化和取值 我们使用花括号包含值&…...
Scala 入门(第一章Scala 环境搭建、插件的安装)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 第 1 章 Scala 入门1.1 概述1.1.1 为什么学习 Scala1.1.2 Scala 发展历史1.1.3 Scala 和 Java 关系1.1.4 Scala 语言特点1.2 Scala 环境搭建1.3 Scala 插件安装1.4 HelloWorl…...
math@多项式@求和式乘法@代数学基本定理
文章目录多项式求和式乘法应用代数学基本定理相关证明高次方程其他关于多项式的参考多项式求和式乘法 S∏j1(∑k1ajk) j∏j1m(∑k1njajk) jS\prod_{j1}\left(\sum\limits_{k1}a_{jk}\right)_{\!\!\!j} \\\prod_{j1}^{m}\left(\sum\limits_{k1}^{n_j}a_{jk}\r…...
Kafka系列之:基于SCRAM和Ranger机制完成动态新增kafka读写账号、赋予账号对指定Topic的读写权限
Kafka系列之:基于SCRAM和Ranger机制完成动态新增kafka读写账号、赋予账号对指定Topic的读写权限 一、需求背景二、添加写用户三、查看用户是否添加到zookeeper中四、查看用户五、赋予用户topic写权限六、生产者配置文件七、ranger给用户权限八、往Topic写数据九、删除添加的用…...
第五十三章 DFS进阶(一)——剪枝优化
第五十四章 DFS进阶(一)——剪枝优化一、什么是剪枝?二、剪枝优化的策略1、优化搜索顺序2、排除等效冗余3、可行性剪枝4、最优性剪枝5、记忆化搜索三、例题1、AcWing 165. 小猫爬山(DFS 剪枝优化)2、AcWing 167. 木棒…...
Java字节码深度知多少?
文章目录1、字节码结构1.1、基本结构1.2、实际观测2、内存表示3、方法调用指令4、invokedynamicEND结语Java真的是长盛不衰,拥有顽强的生命力。其中,字节码机制功不可没。字节码,就像是 Linux 的 ELF。有了它,JVM直接摇身一变&…...
【C++】智能指针(万字详解)
🌈欢迎来到C专栏~~智能指针 (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort目前状态:大三非科班啃C中🌍博客主页:张小姐的猫~江湖背景快上车🚘,握好方向盘跟我有一起打天下嘞!送给自己的一句鸡汤&…...
使用docker配置mysql主从复制
1.新建主服务器容器实例: docker run -p 3307:3306 --name mysql \ -v /docker/mysql/data:/var/lib/mysql \ -v /docker/mysql/conf:/etc/mysql/conf \ -v /docker/mysql/log:/var/log/mysql \ -e MYSQL_ROOT_PASSWORDroot \ -d mysql:5.7 设置容器卷之后…...
v3 异步组件及分包使用
1 app.vue <template> <!-- vue3异步组件必须使用suspense --> <Suspense> <template #default> <!-- 异步组件 --> <SyncVue></SyncVue> </template> <template v-slot:fallback> <!-- 优先显示骨架屏 --> <…...
实用调试技巧【上篇】
🔴本文章是在 Visual Studio 2022(VS2022)编译环境下进行操作讲解 文章目录🥳1. 什么是bug?🥳2.调试有多重要?2.1. 我们是如何写代码的?2.2.调试是什么?2.3.调试的基本步…...
JavaScript 教程
手册简介JavaScript 是世界上最流行的脚本语言。 JavaScript 是属于 web 的语言,它适用于 PC、笔记本电脑、平板电脑和移动电话。 JavaScript 被设计为向 HTML 页面增加交互性。 许多 HTML 开发者都不是程序员,但是 JavaScript 却拥有非常简单的语法。几…...
在SpringBoot里面使用原生的Servlet
在SpringBoot里面使用Servlet 首先在主程序中添加注解主程序添加ServletComponentScan // 加上这个注解之后就可以使用原生的组件了 HttpServlet 继承HttpServlet 重写方法 添加WebServlet 第一种方式使用注解 WebServlet(value "/helsk") public class HelloSe…...
商标被驳回,先别慌!挽回商标有办法
商标注册是一个漫长的等待过程,提交了注册申请之后不代表就能得心应手。商标局在接收到申请后,便会开始各阶段审查,面对不符合条件的商标会予以商标驳回。商标局基于什么原因而驳回注册申请呢?驳回后还有必要进行商标驳回复审吗?今天心周企…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
