这篇文章 由 GitLab Inc. 的联合创始人、首席执行官和董事会主席 Sid Sijbrandij 撰写,本文主要探讨了 Ruby on Rails 作为开发框架的优势,尤其是在 GitLab 项目中的成功应用。作者强调,Ruby on Rails 通过结合良好的架构和易用性,解决了传统开发框架在“可用性”和“结构性”之间的权衡问题。此外,文章讨论了 模块化 的重要性以及为什么 微服务 并不是实现模块化的唯一或最佳方式。最后,文章介绍了 GitLab 如何通过 模块化单体架构 实现了开源与商业模式的结合,促进了创新和扩展。

  1. Ruby on Rails 的优势

    • Ruby on Rails 结合了 PHP 的易用性和 Java 的架构完整性,创建了一个既易于上手又结构良好的 Web 框架。
    • GitLab 选择 Ruby on Rails 作为其开发框架,得益于其易用性和良好的架构,这一选择极大地推动了 GitLab 的成功。
  2. 模块化的必要性

    • 模块化可以缩短开发时间,减少沟通需求,并允许对单个模块进行独立的更改和研究。
    • 作者引用了 David L. Parnas 和 Fred Brooks 的理论,强调模块化有助于减少复杂性和沟通成本。
  3. 微服务的局限性

    • 虽然微服务可以通过操作系统强制模块边界,但这种方法带来了显著的性能和可靠性问题。
    • 微服务架构增加了不必要的复杂性,并且没有保证能够真正实现模块化,反而可能导致“分布式混乱”。
  4. 模块化单体架构的优势

    • GitLab 采用了 模块化单体架构,即一个结构良好、模块化的单一进程程序。这种架构减少了复杂性,并提升了开发效率。
    • 虽然 Rails 有一些性能问题,但 GitLab 通过结合其他技术(如用 Go 编写的 gitaly 守护进程和 PostgreSQL)解决了这些问题。
  5. 开源与商业模式的结合

    • 模块化单体架构使 GitLab 的 Open Core 商业模式成为现实。代码的可读性和良好的结构使得外部贡献者能够轻松参与,推动了产品的快速创新。
    • 相比微服务或插件接口,模块化单体架构避免了过度复杂的部署和集成问题,保持了开发的灵活性。

为什么我们坚持使用 Ruby on Rails

当 David Heinemeier Hansson 创建 Ruby on Rails(采访)时,他结合了自己在 PHP 和 Java 领域的经验。一方面,他不喜欢 Java 语言的冗长和僵化,这让 Java 的 Web 框架变得复杂难用,但他欣赏其结构的严谨性。另一方面,他喜欢 PHP 入门简单,但不喜欢由此带来的项目混乱局面。

Ruby vs. Java

这两者看似是对立的选择:要么是上手简单但杂乱无章,要么是结构清晰但难以使用,就像要从“毒药”中做选择。我们曾对服务器级操作系统(如 Unix)和客户端操作系统(如 Windows 和 MacOS)有过类似的划分,前者稳定但难用,后者易用但容易崩溃。

人们普遍接受了这种“理所当然”的二元对立,直到 NeXT 在坚固的 Unix 基础上引入了一个美观、易用且流畅的图形用户界面 (GUI)。如今,“服务器级” Unix 不仅支持美观的桌面 GUI,还能运行在大多数手机和智能手表上。

由此可见,易用性和崩溃率之间并没有必然的联系,它们的关联只是历史偶然。而在 Web 框架中,易用性和项目混乱度同样也不是必然联系:它们是两个独立的维度。

approachability and messiness

这两个独立的维度在右下角开辟了一个理想的空白区:一个易于上手且结构清晰的 Web 框架。凭借其坚实的、小型语言 (Smalltalk) 元编程基础和出色的 Unix 集成,Ruby 成为 DHH 实现这一目标的完美载体,从而创造出 Rails:一个极具易用性、高效且结构良好的 Web 框架。

a well-structured framework

当 GitLab 联合创始人 Dmitriy Zaporozhets 决定开发一个用于运行他(以及大家)版本控制服务器的软件时,他也有 PHP 背景。但他没有沿用熟悉的 PHP,而是选择了 Rails。Dmitriy 的选择或许有着敏锐的眼光,也可能是偶然之举,但它确实帮助 GitLab 获得了极大的成功,因为 David 成功实现了他的目标:一个既易于使用,又具备良好架构的 Rails 框架。

为什么要模块化?

在前面的讨论中,我们默认模块化是理想的特性,但我们也看到了,盲目假设某件事是理所当然的可能存在风险。那么,为什么在某些情况下模块化确实是必要的呢?

在他 1971 年的论文《“用于将系统分解为模块的标准”》中,David L. Parnas 提出了模块化系统的理想优势:

  • 开发时间应当缩短,因为各小组可独立开发不同的模块,而无需过多交流。
  • 可以对某个模块进行重大修改或改进,而不必改动其他模块。
  • 可以一次只专注于系统的一个模块进行研究。

降低沟通需求的重要性后来被 Fred Brooks 在 《人月神话》 一书中进一步强调,增加沟通成本是导致“给已延期的软件项目增加人手只会使其更晚完成”的主要原因之一。

我们不需要微服务

模块化一直以来都是理想化但难以实现的目标,大多数系统的默认架构都是“大泥球”。因此,设计者们从可能是现存最大的软件系统——万维网中汲取灵感。万维网天然具有模块化特性,因为它只能以这种方式运行。

通过单独的进程和微服务架构来组合模块,并使用REST 进行交互,这种方式确实能通过操作系统强制模块边界,但也带来了巨大的开销。这是一种为实现模块化而采用的过于复杂的方法。

运行这种分布式系统所需的成本和困难相当可观,性能和可靠性问题在分布式计算的谬论中已有详细说明。简单来说,性能和可靠性成本极高,函数调用的时间在纳秒级且几乎不会失败,而网络操作则慢了几个数量级,并且可能失败。如果需要在多个服务中进行故障排查,诊断问题将变得更加复杂,而工具支持非常有限。 要成功运行微服务,你需要一个相当复杂的 DevOps 组织。如果你的业务规模足够大,那么这种复杂性是必须的,但你很可能并非“谷歌级别”

即便你认为自己能够处理这些问题,仍需注意的是,这些附加的复杂性是在原始问题的固有复杂性之外的,微服务并不能减少系统的复杂性。而且,即使是期望中的模块化提升也并不一定能够实现,通常的结果反而是形成一个分布式“大泥球”

Monorails

通过让优秀的架构易于使用并提升生产力,Rails 使 GitLab 能够构建一个modular monolith(模块化单体架构)。模块化单体架构是分布式“大泥球”的对立面:一个结构清晰、设计良好、高度模块化的系统,以单一进程运行,并尽可能保持“平淡无奇”。

尽管将 GitLab 构建为单体架构对我们大有裨益,但我们并不固守这种结构。架构应当服务于需求,而不是反过来。尽管 Rails 对我们的业务非常适合,但它确实存在一些问题,其中之一是性能瓶颈。幸运的是,大多数代码库中只有极少部分代码对性能要求严格。我们使用 Go 语言编写的 gitaly 守护进程处理具体的 git 操作,并使用 PostgreSQL 进行非代码数据的持久化。

开放核心

最后但同样重要的是,我们的模块化单体架构使我们开放核心商业模式从一种美好的理论变成了实际现实。虽然这并非 Rails 单独能够实现的,而是我们出色的贡献者和工程师的共同努力,但 Rails 确实为此奠定了坚实的基础。

为了真正获得开源的实际收益,我们所提供的源代码必须对贡献者足够友好。为了在接受多样化的贡献的同时保持架构的完整性,并确保开源组件与闭源组件之间有清晰的界限,代码结构必须非常合理。听起来是不是很熟悉?

难道不应该有一个合适的插件接口?或者更好的是一个基于微服务的服务接口?简言之:不。这些方式不仅在部署和集成上带来了极大障碍,远超出了“我对源代码做了一个小改动”的初衷,它们还强加了僵化的架构限制。预见所有未来的扩展点是不可能的,幸运的是,我们并没有陷入这种困境,也不必如此。

通过我们“稳健”的模块化单体架构,用户和其他第三方开发者能够并且确实为核心产品贡献新功能,为我们带来了巨大的杠杆效应,同时也提升了创新的速度和可扩展性。