Ente: 我们的 Monorepo 经验

在这里插入图片描述

原文:manav - 2024.10.29

九个月前,我们切换到了 monorepo。在此,我将介绍我们迄今为止的切换经验。

这并不是一份规范性的建议,而是一个经验的分享,目的是希望能够帮助其他团队做出明智的决策。

与大多数岔路不同,我们走过了两条路。因此,我会先描述导致我们改变的历史,概述我们在类似的情况中已经体验过的非 monorepo 方案,并因此能够更好地进行对比。

平台与 monorepo

Ente 的诞生可以追溯到五年前。它原本是一个端到端加密平台,用于存储 Vishnu 的所有个人数据,但后来发生了两件事: Vishnu 意识到需要这样一个平台的不仅仅是他自己,他还意识到要实现他的愿景需要做大量的工作。

于是,他从一个人变成了一个团队,并且不再试图处理所有的个人数据,而是将重点转移到其中的一个方面:Ente Photos,以此为起点让“飞船”起飞。对外界观察者来说,这似乎只是一个照片应用(实际上这确实是我们当前的具体目标),但背后驱动这一切的是我们对人类隐私权的坚持,即所有形式的个人数据都应受到保护。

我为什么要描述这些?因为从这个愿景来看,Ente 不是一个单一的应用程序,而是一个平台,将其代码存储在一个 monorepo 中是一个符合理念的选择。

这类似于 Linux 内核。大多数人不了解的是,按多项可量化指标衡量,全球最大的开源项目——Linux 内核本身——也是一个 monorepo。尽管它被称为“内核”,但实际上它是一整个平台,包括设备驱动程序等,代码组织为 monorepo 正是这种理念的体现。

坚持将 Ente 视为一个平台不仅仅是理念上的选择,它也带来了实际的益处。

例如,几年前,我们意识到还没有一款具有云备份功能的开源端到端加密 OTP 应用程序。于是我们为了自己的使用而构建了一款,因为是基于我们为照片应用创建的基础设施,实现并不难。

如今,这个副产品已成为世界上最受欢迎的具有上述特性的 OTP 应用。这看似是个意外,但其实不然,我们的计划一直是这样的:先建立一个稳固的平台,然后逐一处理我们需要的各种定制应用程序,以便更好地处理不同形式的数据。

微型代码仓库(Microrepos)

从理念上讲,Ente 作为一个 monorepo 是最合适的选择。但由于产品演变的历史因素,最开始并不是这样。硬件设备转变为软件,服务器组件在我们有能力进行审计之前是闭源的。像 Auth 这样的周末项目超出了它们的初衷,等等。

让我们倒带回两年前(仅仅为了选择一个大致对称的时间点)。虽然我们在包括开发人员数量在内的所有产品方面都在增长,但我们在增加工程人员方面非常谨慎,所以开发人员的数量并没有增加太多。因此,差不多是同样数量的开发人员在处理相同数量的产品(Ente Photos、Ente Auth),并同时支持多个平台(移动端、网页端、桌面端、服务器端、CLI)。

两年前,这些代码库分散在十几个仓库中。

到了今年二月,我们决定花时间完成服务器端开源的任务。这是一个很自然的时机来控制代码库的分散,于是我们借此机会切换到了 monorepo。

因此,作为一个规模相似的团队,做着类似的工作,我们已经体验了约一年分散的微型仓库设置,以及约一年集成的 monorepo 设置。

总结

如果要总结区别的话:切换到 monorepo 后,变化不大,而细微的变化都是正面的

我们对此并不感到意外。其中大多数人对代码仓库的组织方式并不十分在意,总体上对这种改变也没有太高的期望。大家的整体感觉是 monorepo 可能会更好,所以为什么不试试呢?既然没有人反对这个选择,我们就这么做了,但我们并没有试图通过这次改变“解决”什么问题。

事实上,整体上变化不大。我们依然对开发速度感到满意,所以它并没有拖慢我们。然而,确实有许多小的改进,所以接下来的部分我将深入探讨这些改进。

更少的重复劳动

这是最大的实际收益。我们需要做的重复劳动大大减少了。

举个例子,考虑以下的 pull request。它修改了用于计算设备上人脸嵌入的机器学习模型。

在这里插入图片描述

这个更改影响了(1)照片移动端应用,(2)照片桌面端应用,(3)照片网页端应用,以及(4)机器学习的基础代码。

在之前的分仓库模式下,这将是四个不同的 pull request,分别提交到四个不同的仓库中,并且需要通过评论将它们联系在一起以供日后参考。

现在,这是一份 pull request。容易审查,容易合并,容易回滚。

更少的子模块

子模块是一个让人恼火但确实有效的解决方案。问题是真实存在的,因此需要解决方案,而子模块确实是一个合适的解决方案,但它们仍然令人恼火。

这就是说,我们感谢 git 子模块的存在,它是解决实际代码组织问题的一种方法,但我们希望不需要使用它们。

Monorepo 减少了那些本应需要子模块的地方,因此这也是一个优势。

举个例子,之前 Ente Photos 的网页端和桌面端代码库之间是子模块关系。每次需要发布或推动重要的更改到主分支时,都会涉及到繁琐的 PR 操作。现在这些都不需要了。这两个相互依赖的代码现在可以在同一个提交中直接引用彼此,变更可以原子性地完成。

更多的 Stars

这是最大的营销收益。之前我们的 Stars 分散在十几个仓库中。如果每个仓库有一千个 Stars,我们总共有 12k Stars,但由于人的心理和 GitHub 推荐算法的工作方式,这远不如一个拥有 12k Stars 的单一仓库来的有影响力。

简单

我们在切换时的一个顾虑是,这可能会影响开发速度。我们以为会需要发明各种机制和约定来避免互相干扰。

但这些顾虑被证明是多余的。我们没有发明任何东西,只是静观其变,结果并不需要任何新方案。因此,对个人开发者来说,这次切换是轻松的,因为我们没有要求团队的任何成员改变他们的工作流程。

目前为止,也没有“仓库范围”的指导原则,除了两个:

  1. 不要有仓库范围的指导原则
  2. 不要动根目录文件夹

就是这样。每个文件夹内,或者每个子团队内部,可以自由选择任何组织方式、编码约定等等。

我意识到,这种轻松对我们来说,可能是由于团队规模较小,以及我们对彼此能力的高度信任。而这两个因素可能无法在其他团队中复制。

长期重构

跨仓库的重构需要比在同一个仓库内的重构更多的勇气。技术上来说,两者没有区别,但心理上的障碍却有所不同。

举个例子,我们已经将许多不同的网页应用合并到一个类似的设置中,而无需事先制定详细的计划。这一切都很自然地发生了,因为我们能够看到它们“彼此相邻”,代码复用的机会变得显而易见。

连接感

这种“在共享空间中工作但不在同一文件夹中工作”的方式,让我们比起以前单独或者以子团队的形式在各自的仓库提交代码时,感觉更紧密相连。

之前,很容易沉浸在各自的工作中(这是好事),但有时也会让人觉得自己只是在处理一个小部分,而无法看到整体(这不是好事)。

现在,大家仍然可以沉浸在自己的“文件夹”中,保留了这种沉浸感的好处。但也有额外的微妙提示让我们看到自己的工作是如何与整体相互关联的。因此,这是一种双赢的局面。

我所描述的可能有些抽象,所以让我举个例子。每当执行 git pull 时,会看到团队成员正在处理的所有变更。最近更改的文件名,文件中的更改数,最近的分支名,最近推送的标签。这些单独来看都是低信息量且不精确的信息载体,我甚至不会有意识地去看它们。

但随着时间的推移,我发现这些“环境提示”无意识地、自动地让我对周围发生的事情有了极好的感知。哪些功能正在开发,完成的阶段如何,哪些 bug 修复被推送了,最近发布了哪些版本。

类似的偶然信息交换也会发生在我打开 pull request 页面时,我会不经意间瞥见其他人正在处理的内容。

最棒的是,这一切都是颠覆性的、毫不费力的。每个人都在做自己的事,而仅仅因为大家都在这个共享的数字空间里工作,就自然地产生了一种意识和连接感。

总结

这篇文章已经很长了,远超我原本的预期,所以就此打住。

我本可以提供一些建议,但我认为没有什么特别的技术诀窍是必须的。在切换之前让我感到困扰的一个问题是我们将如何管理 GitHub 工作流,但事实证明这很简单,因为我们可以将 GitHub 工作流的范围限定为仅在特定文件夹的更改上运行。

从工程师的角度来看,回顾性文档如果没有“优缺点”部分是不完整的,但到目前为止,还没有发现任何对我们有影响的缺点,所以请原谅这部分内容的缺失。

从个人角度来说,我最喜欢 monorepo 的是,它让我感到自己像是“巨轮”的一部分,这艘巨轮正在无情地驶向完美,并且已经获得了不可阻挡的势头。我现在写的代码不再是一个孤立的 Web 组件,或一个 goroutine,或一个小的文档修复,而是这个单一平台的一部分——一个将超越我生命的平台。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

css:还是语法

emmet的使用 emmet是一个插件&#xff0c;Emmet 是 Zen Coding 的升级版&#xff0c;由 Zen Coding 的原作者进行开发&#xff0c;可以快速的编写 HTML、CSS 以及实现其他的功能。很多文本编辑器都支持&#xff0c;我们只是学会使用它&#xff1a; 生成html结构 <!-- emme…

常见计算机网络知识整理(未完,整理中。。。)

TCP和UDP区别 TCP是面向连接的协议&#xff0c;发送数据前要先建立连接&#xff1b;UDP是无连接的协议&#xff0c;发送数据前不需要建立连接&#xff0c;是没有可靠性&#xff1b; TCP只支持点对点通信&#xff0c;UDP支持一对一、一对多、多对一、多对多&#xff1b; TCP是…

javascript实现国密sm4算法(支持微信小程序)

概述&#xff1a; 本人前端需要实现sm4计算的功能&#xff0c;最好是能做到分多次计算。 本文所写的代码在现有sm4的C代码&#xff0c;反复测试对比计算过程参数&#xff0c;成功改造成sm4的javascript代码&#xff0c;并成功验证好分多次计算sm4数据 测试平台&#xff1a; …

深度解读AI在数字档案馆中的创新应用:高效识别与智能档案管理

一、项目背景介绍 在信息化浪潮推动下&#xff0c;基于OCR技术的纸质档案电子化方案成为解决档案管理难题的有效途径。该方案通过先进的OCR技术&#xff0c;能够统一采集各类档案数据&#xff0c;无论是手写文件、打印文件、复古文档还是照片或扫描的历史资料&#xff0c;都能实…

C++ | Leetcode C++题解之第554题砖墙

题目&#xff1a; 题解&#xff1a; class Solution { public:int leastBricks(vector<vector<int>>& wall) {unordered_map<int, int> cnt;for (auto& widths : wall) {int n widths.size();int sum 0;for (int i 0; i < n - 1; i) {sum wi…

【机器学习】强化学习(1)——强化学习原理浅析(区分强化学习、监督学习和启发式算法)

文章目录 强化学习介绍强化学习和监督学习比较监督学习强化学习 强化学习的数学和过程表达动作空间序列决策策略&#xff08;policy&#xff09;价值函数&#xff08;value function&#xff09;模型&#xff08;model&#xff09; 强化学习和启发式算法比较强化学习步骤代码走…

模糊搜索:在不确定性中寻找精确结果

目录 模糊搜索&#xff1a;在不确定性中寻找精确结果 一、引言 二、模糊搜索的背景 三、模糊搜索的原理 1、编辑距离&#xff08;Levenshtein Distance&#xff09;&#xff1a; 2、Jaccard 相似系数&#xff1a; 3、Soundex 算法&#xff1a; 4、TF-IDF&#xff08;词…

MyBatis5-缓存

目录 一级缓存 二级缓存 MyBatis缓存查询的顺序 整合第三方缓存EHCache 一级缓存 一级缓存是 SqlSession 级别的&#xff0c;通过同一个 SqlSession 查询的数据会被缓存&#xff0c;下次查询相同的数据&#xff0c;就会从缓存中直接获取&#xff0c;不会从数据库重新访问 一…

95.【C语言】数据结构之双向链表的头插,头删,查找,中间插入,中间删除和销毁函数

目录 1.双向链表的头插 方法一 方法二 2.双向链表的头删 3.双向链表的销毁 4.双向链表的某个节点的数据查找 5.双向链表的中间插入 5.双向链表的中间删除 6.对比顺序表和链表 承接94.【C语言】数据结构之双向链表的初始化,尾插,打印和尾删文章 1.双向链表的头插 方法…

24-11-9-读书笔记(三十二)-《契诃夫文集》(六)上([俄] 契诃夫 [译] 汝龙)药品是甜的,真理是美的,咖啡是苦的,生活是什么啊?

文章目录 《契诃夫文集》&#xff08;六&#xff09;上&#xff08;[俄] 契诃夫 [译] 汝龙&#xff09;药品是甜的&#xff0c;真理是美的&#xff0c;咖啡是苦的&#xff0c;生活是什么啊&#xff1f;目录阅读笔记1. 新年的苦难2. 香槟3. 乞丐4. 仇敌5.薇罗琪卡6.在家里7. 太早…

【从零开始鸿蒙开发:01】自定义闪屏页

文章目录 大体介绍文件介绍各部分代码SplashPage.etsIndex.etsHomePage.etsroute_map.jsonmodule.json5 流程 大体介绍 文件介绍 其中&#xff1a; pages为我们的页面内容&#xff08;我个人理解功能性小于activity但是大于fragment&#xff09;route_map.json 为自定义的路由…

【Spring】获取Cookie和Session(@CookieValue()和@SessionAttribute())

文章目录 获取 Cookie传统获取 Cookie简洁获取 Cookie&#xff08;注解&#xff09; 获取 SessionSession 存储和获取简洁获取 Session (1)简洁获取 Session (2) 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给…

【机器学习】任务十:从函数分析到机器学习应用与BP神经网络

目录 1.从函数分析到机器学习应用 1.1 3D曲面图可视化报告 1.1.1 目标 1.1.2 代码分析 1.1.3 结果分析 1.1.4 观察与总结 1.1.5 结论 1.2 一元函数梯度计算报告 1.2.1 目标 1.2.2 代码分析 1.2.4 计算结果 1.2.5 优势与意义 1.2.6 结论 1.3 一元函数梯度和二阶导…

ios打包文件上传App Store windows工具

在苹果开发者中心上架IOS APP的时候&#xff0c;在苹果开发者中心不能直接上传打包文件&#xff0c;需要下载mac的xcode这些工具进行上传&#xff0c;但这些工具无法安装在windows或linux电脑上。 这里&#xff0c;我们可以不用xcode这些工具来上传&#xff0c;可以用国内的香…

Rust @绑定(Rust@绑定)(在模式匹配的同时将值绑定到变量)

文章目录 Rust中的绑定基础概念示例&#xff1a;基本模式匹配 绑定的使用示例&#xff1a;范围匹配并绑定变量 深入探索绑定的好处示例&#xff1a;复杂数据结构中的应用 总结 附加 Rust中的绑定 Rust 语言以其强类型系统和内存安全的特性著称。在进行模式匹配时&#xff0c;R…

JVM知识点大全(未完...)

JVM运行时数据区域 堆 堆是Java虚拟机中用于存储对象的主要区域&#xff0c;包括字符串常量池。绝大多数对象都是在堆中创建的&#xff08;少部分对象可能会在栈上分配&#xff09;。为了更好地进行垃圾回收&#xff0c;堆被划分为年轻代和老年代两部分。年轻代又被进一步分为E…

Nginx(编译)+Lua脚本+Redis 实现自动封禁访问频率过高IP

1.安装lua 1.1安装LuaJIT yum install readline-devel mkdir -p lua-file cd lua-file/ wget https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.0.5.tar.gz tar -zxvf LuaJIT-2.0.5.tar.gz cd LuaJIT-2.0.5 make && make install PREFIX/usr/local/luajit 1.2…

Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测

Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测 目录 Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量回归预…

Java - SpringBoot之logback设置日期分割并设置指定时间自动清除,Linux启动运行

一、SpringBoot之logback-spring.xml配置 在Spring Boot中&#xff0c;要设置日志按照日期进行分割输出&#xff0c;并设置日志文件的大小自动清除&#xff0c;可以使用logback日志框架的配置 1、创建文件 在项目的resources目录下&#xff0c;创建logback-spring.xml文件 …

window11安装elasticsearch+Kibana

1、下载elasticsearch与elasticsearch 下载elasticsearch 查看elasticsearch对应的Kibana版本 下载elasticsearch解压后文件目录如下 可执行脚本文件,包括启动elasticsearch服务、插件管理、函数命令等 bin配置文件目录,如elasticsearch配置、角色配置、jvm配置等 conf 默认…