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 的单一仓库来的有影响力。
简单
我们在切换时的一个顾虑是,这可能会影响开发速度。我们以为会需要发明各种机制和约定来避免互相干扰。
但这些顾虑被证明是多余的。我们没有发明任何东西,只是静观其变,结果并不需要任何新方案。因此,对个人开发者来说,这次切换是轻松的,因为我们没有要求团队的任何成员改变他们的工作流程。
目前为止,也没有“仓库范围”的指导原则,除了两个:
- 不要有仓库范围的指导原则
- 不要动根目录文件夹
就是这样。每个文件夹内,或者每个子团队内部,可以自由选择任何组织方式、编码约定等等。
我意识到,这种轻松对我们来说,可能是由于团队规模较小,以及我们对彼此能力的高度信任。而这两个因素可能无法在其他团队中复制。
长期重构
跨仓库的重构需要比在同一个仓库内的重构更多的勇气。技术上来说,两者没有区别,但心理上的障碍却有所不同。
举个例子,我们已经将许多不同的网页应用合并到一个类似的设置中,而无需事先制定详细的计划。这一切都很自然地发生了,因为我们能够看到它们“彼此相邻”,代码复用的机会变得显而易见。
连接感
这种“在共享空间中工作但不在同一文件夹中工作”的方式,让我们比起以前单独或者以子团队的形式在各自的仓库提交代码时,感觉更紧密相连。
之前,很容易沉浸在各自的工作中(这是好事),但有时也会让人觉得自己只是在处理一个小部分,而无法看到整体(这不是好事)。
现在,大家仍然可以沉浸在自己的“文件夹”中,保留了这种沉浸感的好处。但也有额外的微妙提示让我们看到自己的工作是如何与整体相互关联的。因此,这是一种双赢的局面。
我所描述的可能有些抽象,所以让我举个例子。每当执行 git pull 时,会看到团队成员正在处理的所有变更。最近更改的文件名,文件中的更改数,最近的分支名,最近推送的标签。这些单独来看都是低信息量且不精确的信息载体,我甚至不会有意识地去看它们。
但随着时间的推移,我发现这些“环境提示”无意识地、自动地让我对周围发生的事情有了极好的感知。哪些功能正在开发,完成的阶段如何,哪些 bug 修复被推送了,最近发布了哪些版本。
类似的偶然信息交换也会发生在我打开 pull request 页面时,我会不经意间瞥见其他人正在处理的内容。
最棒的是,这一切都是颠覆性的、毫不费力的。每个人都在做自己的事,而仅仅因为大家都在这个共享的数字空间里工作,就自然地产生了一种意识和连接感。
总结
这篇文章已经很长了,远超我原本的预期,所以就此打住。
我本可以提供一些建议,但我认为没有什么特别的技术诀窍是必须的。在切换之前让我感到困扰的一个问题是我们将如何管理 GitHub 工作流,但事实证明这很简单,因为我们可以将 GitHub 工作流的范围限定为仅在特定文件夹的更改上运行。
从工程师的角度来看,回顾性文档如果没有“优缺点”部分是不完整的,但到目前为止,还没有发现任何对我们有影响的缺点,所以请原谅这部分内容的缺失。
从个人角度来说,我最喜欢 monorepo 的是,它让我感到自己像是“巨轮”的一部分,这艘巨轮正在无情地驶向完美,并且已经获得了不可阻挡的势头。我现在写的代码不再是一个孤立的 Web 组件,或一个 goroutine,或一个小的文档修复,而是这个单一平台的一部分——一个将超越我生命的平台。
相关文章:
Ente: 我们的 Monorepo 经验
原文:manav - 2024.10.29 九个月前,我们切换到了 monorepo。在此,我将介绍我们迄今为止的切换经验。 这并不是一份规范性的建议,而是一个经验的分享,目的是希望能够帮助其他团队做出明智的决策。 与大多数岔路不同&…...
Kafka java 配置
前言: 大家好,大家在springboot项目中,经常采用 KafkaListener 做为消费者。这个是spring为我们封装的。 但是某些情况 注解的方式并不能满足需求。这个时候就需要手动版本了。 介绍: 我们已经集成spring-Kafka 就不需要再…...
网络安全现状:复杂的威胁形势导致压力水平飙升
《2024 年网络安全状况》报告深入分析了当前网络安全挑战和趋势。 该报告重点介绍了几个关键的关注领域,包括人员短缺、技能差距、不断演变的威胁和预算限制,同时还指出了取得进展的领域,例如对威胁响应能力的信心增强以及对网络风险评估的认…...
【机器学习】强化学习(1)——强化学习原理浅析(区分强化学习、监督学习和启发式算法)
文章目录 强化学习介绍强化学习和监督学习比较监督学习强化学习 强化学习的数学和过程表达动作空间序列决策策略(policy)价值函数(value function)模型(model) 强化学习和启发式算法比较强化学习步骤代码走…...
【SoC设计指南 基于Arm Cortex-M】学习笔记1——AMBA
AMBA简介 先进微控制器总线架构(Advanced Microcontroller Bus Architecture,AMBA)是用在arm处理器上的片上总线协议规范集。 AMBA总线协议规范集包含AHB、APB、AXI等。 AHB:先进高性能总线(Advanced High-performance Bus) APB&…...
flutter鸿蒙模拟器 Win环境调试报错问题记录(暂未解决)
前情提要: 1、flutter项目已经正确生成了ohos项目 2、flutter和鸿蒙的环境变量配置正确 3、ohos项目执行flutter build hap成功 4、没有真机,使用win环境创建的x86模拟器 问题状态 使用模拟器运行ohos,控制台提示“安装HAP 报 code:9568347错…...
详解Rust标准库:HashSet
## 查看本地官方文档安装rust后运行 rustup doc查看The Standard Library即可获取标准库内容 std::collections::hash_set::HashSet定义 HashSet是一种集合数据结构,它只存储唯一的元素。它主要用于检查元素是否存在于集合中,或者对元素进行去重操作&…...
记录学习react的一些内容
由于是在公司实际项目中学习,所以不是很完整 需要一点一点的学 1.React.useState 类似于vue中的ref 可以修改状态 但是是异步的 感觉不好用 const [wishData, setWishData] React.useState<any>(null); 只能使用setxxx来修改 2.useEffect(()>{},[]) 类…...
json绘制热力图
首先需要一段热力信息的json,我放在头部了。 然后就是需要de-geo库了。 实现代码如下: import * as d3geo from d3-geoimport trafficJSON from ../assets/json/traffic.jsonlet geoFun;// 地理投影函数// let info {max: Number.MIN_SAFE_INTEGER,mi…...
linux 下查看程序启动的目录
以azkaban为例 第一步、ps -ef | grep azkaban 查询出进程号 第二步、cd /proc/ 第三步 、cd 进程号 第四部 ll 查看详情 查看jar 位置 查看jar 启动命令...
书生浦语第四期基础岛L1G2000-玩转书生「多模态对话」与「AI搜索」产品
文章目录 一、MindSearch二、书生浦语三、书生万象四、进阶任务 一、MindSearch MindSearch 是一个开源的 AI 搜索引擎。它会对你提出的问题进行分析并拆解为数个子问题,在互联网上搜索、总结得到各个子问题的答案,最后通过模型总结得到最终答案。书生浦…...
保护Kubernetes免受威胁:容器安全的有效实践
安全并非“放之四海而皆准”的解决方案,相反地,它更多的是一个范围,受其应用的特定上下文的影响。安全领域的专业人士很少宣称什么产品是完全安全的,但总有方法可以实现更强的安全性。在本文中,我们将介绍各种方法来支…...
【客观理性深入讨论国产中间件及数据库-科创基础软件】
随着国产化的进程,越来越多的国企央企开始要求软件产品匹配过程化的要求, 最近有一家银行保险的科技公司对行为验证码产品就要求匹配国产中间件, 于是开始了解国产中间件都有哪些厂家 一:国产中间件主要产品及厂商 1 东方通&…...
MFC中Excel的导入以及使用步骤
参考地址 在需要对EXCEL表进行操作的类中添加以下头文件:若出现大量错误将其放入stdafx.h中 #include "resource.h" // 主符号 #include "CWorkbook.h" //单个工作簿 #include "CRange.h" //区域类,对Excel大…...
AWS S3在客户端应用不能使用aws-sdk场景下的文件上传与下载
简介 通常情况下,应用程序上传文件到AWS S3,会使用aws-sdk,但是有些情况下,客户端应用会有安装限制,比如不能安装aws-sdk,此时我们就需要通过其他方式实现文件上传与下载。 这里我们提供一个服务端&#…...
深入解析 Transformers 框架(四):Qwen2.5/GPT 分词流程与 BPE 分词算法技术细节详解
前面我们已经通过三篇文章,详细介绍了 Qwen2.5 大语言模型在 Transformers 框架中的技术细节,包括包和对象加载、模型初始化和分词器技术细节: 深入解析 Transformers 框架(一):包和对象加载中的设计巧思与…...
【Python-AI篇】K近邻算法(KNN)
0. 前置----机器学习流程 获取数据集数据基本处理特征工程机器学习模型评估在线服务 1. KNN算法概念 如果一个样本在特征空间中的K个最相似(即特征空间中最邻近)的样本中大多数属于某一个类别,则该样本也属于这一个类别 1.1 KNN算法流程总…...
aws xray如何实现应用log和trace的关联关系
参考资料 https://community.aws/tutorials/solving-problems-you-cant-see-using-aws-x-ray-and-cloudwatch-for-user-level-observability-in-your-serverless-microservices-applicationshttps://stackoverflow.com/questions/76000811/search-cloudwatch-logs-for-aws-xra…...
centos服务器登录失败次数设定
实现的效果 一台centos服务,如果被别人暴力或者登录次数超过多少次,就拒绝或者在规定时间内拒绝ip登录。这里使用的是fail2ban 安装fail2ban sudo yum install epel-release -y # 先安装 EPEL 源 sudo yum install fail2ban -y配置fail2ban # 复制默…...
实时高效,全面测评快递100API的物流查询功能
一、引言 你是否曾经在网购后焦急地等待包裹,频繁地手动刷新订单页面以获取最新的物流信息?或者作为一名开发者,正在为如何在自己的应用程序中高效地实现物流查询功能而发愁?其实,有一个非常好用的解决方案——快递10…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
全面解析各类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…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...
