Joakim Tendstrand 是 Clojure 和 Datomic 的架构师和开发人员,最近向作者强烈推荐了自己团队的产品 Polylith ,这个名字结合了“很多”和“石头”的概念。它是一种软件体系结构,利用许多构件来组合系统,所有构件就像工作在一个地方一样。Polylith 克服了单体、微服务、无服务架构的一些缺点,本文希望从概念和实现上对这个新架构做一个介绍,以引起开发使用者的兴趣可以去采纳并应用它。
Polylith 的特点是功能简单和可组合性。它可以帮助我们构建简单、可维护、可测试和可扩展的系统。它像乐高(Lego™)一样利用具有共享函数属性的构件块来构建系统。
将构件组合成系统:
当前架构是否可以改进?
下面对比了开发和生产体验中的三个主流软件架构:
- 单体(Monolith), 一种软件体系结构,其中代码存储在单个代码库中并部署为单个人工制品。
- 微服务(Microservice), 一种由小型可独立部署的服务组成的软件体系结构,每个服务都在一个单独的进程中运行,并通过网络与其他网络进行通信。
- 无服务器 (Serverless),一种基于云计算执行模型的软件体系结构,云供应商动态管理机器资源的分配。
开发体验
生产体验
这些体系结构提供了大量如何部署系统的指导,但对于如何在每个系统中构造代码指导很少。
- 现有软件体系结构所经历的偶然的、高度复杂性以及与开发相斥的挫折。
- 我们想要一个架构,它能给代码库一个可理解、可维护、可重用和可测试的结构。我们想要一个一流的开发体验,当我们作出改变时,能导航和重构所有的代码,并快速反馈。我们希望建立可伸缩的系统,但避免不必要的复杂性被“过早分发”。
- 我们想让它能简单、快速、有趣地构建任何大小的系统。
Polylith 是什么?
为了帮助解释这个架构,Joakim 和他的团队发明了一种隐喻(metaphor),一个基于 Clojure 的体系结构,还演化了一个开发工具。
完整的 Polylith 体验
- 隐喻帮助理解和交流系统。
- 体系结构将系统构造为高级的构件,并创建有效的开发环境来处理所有代码。
- Polylith 工具使开发者能快速、有趣地与 Polylith 系统一起工作。
介绍
隐喻有助于理解 Polylith 的建筑概念。Polylith 系统由三种类型的构件组成:组件(Component)、基底(Base)和库(Library)。每种类型的构件都表示打包代码的特定方法,其目标是使它们简单地组合成系统。
基本知识
隐喻建立在两个基本的功能概念上:函数和命名空间。
功能
函数具有许多属性,封装性、简单性、无状态、纯度。这些属性使函数(尤其是纯函数)具有牢固的、可组合和可测试的代码单元。这就是为什么选择在隐喻中使用类似乐高的砖块,因为乐高就是一个牢固的、可组合的积木。
“乘法”乘以其自变量并返回结果
绿色的部位表示函数的实现
红色的部位表示对另一个函数的依赖关系
底部的方形孔代表函数的签名
签名: 函数的名称、参数和返回结果。
组合函数
让我们来看一个更大的函数,以及函数调用在隐喻中的作用。
- "圆形区域"计算给定半径圆的面积。
- 绿色部分表示函数的实现。
- 黄色部分代表了我们的“乘法”函数的依赖性。
- 底部的圆孔代表函数的签名。
命名空间
名称空间打包了相关功能,作为一个共同术语并被赋予一个名称。在 Java 中,这个概念被称为包,在其它语言中,它被称为模块。让我们看看如何将函数打包到 Clojure 中的命名空间中:
组件
组件是 Polylith 的核心构件,将系统划分为封装的和可组合的代码块。通过将它们的实现与它们的接口分离来实现它们的封装和可组合性。
浅绿色层是接口,深绿色层是实现
设计良好的组件具有单一职责、描述性名称,并且公开了一组仅与其职责相关的清晰功能。举几个例子,一个组件可以:
- 是某领域实体,如用户、帐户或发票
- 管理与外部系统的集成
- 处理特定的基础设施,如电子邮件、日志记录或存储
基底
基底是 Polylith 的基础构件,是一个系统与外界沟通的门户。基底通过将实现与接口分离来实现封装。
浅蓝色层是接口(API),深蓝色层是实现
每一个基底都有一个责任:向外界展示一个系统的功能。在 Polylith 中,可以在基底中自由地使用任何 API 技术,例如 REST、gRPC、GraphQL 或只是简单的旧的命令行。
基底和组件之间的区别是基座具有平的底部。这意味着基底是不可合成的,它们不能被其他构建块所依赖。基底总是位于 Polylith 的底部。
库
库(Library)是 Polylith 的“屋顶”构件,是别人的代码,内部代码依赖于这些代码。
系统
Polylith 的构件组合方法使得构建系统既简单又有趣。一旦设计好了基底的 API,那么只需要组成一组正确的组件和库,这些组件和库就能实现了我们公开的功能。让我们来看看一个完整的系统:
系统:一个基底,两个组件,三个库
生态系统
生态系统(ecosystem)是 Polylith 的扩展解决方案。它们是一个系统的集合,为外部世界提供一套统一的功能。在 Polylith 中,它们是通过一个“电缆”连接的,它从系统 B 中的一个组件的外部依赖性扩展到系统 A 的一个端点。
系统 B 连接到系统 A 的 API 提供的端点
Polylith 架构的设计主要围绕两个目标:简单性和速度。简单性来自于保持概念的独立性,速度来自于把我们所有的代码放在一个地方。这一节介绍 Polylith 是如何利用 Clojure 来实现这两个目标的,并用一个 RealWorld 的示例程序来描述(代码参考文章末链接)。
工作空间
工作空间“workspace”是 Polylith 项目中的根文件夹,这是使用组件组装系统的地方。
可以把工作空间想象成一个办公桌:一个与我们的积木一起工作的地方。它用“抽屉”来保存构件,用“架子”来组装系统,还有一个“工作台”作为开发环境。
- 工作空间就像一个有抽屉、架子和工作台的书桌:
- “抽屉”是我们保存基底、组件和接口的文件夹。
- “架子”是我们配置和存储系统的文件夹。
- “工作台”是我们保持开发环境的文件夹。
基底
基底“bases”文件夹是一个“抽屉”,它保存了工作空间中的所有基底。
每个基底都有一个标准的 Clojure 项目结构:使用 Polylith.clj 文件(类似于 pom.xml,被用于 Maven 和 Java),一个 readme.md 文件,一个资源文件夹,一个 src 文件夹,和一个测试文件夹。每个基底都是一个项目,可以将它们编译为单个的工件,这保证了它们与所有其他构件解耦。
“api”命名空间公开了所有的基底
组件
组件“components”文件夹是另外一个“抽屉”,它保存了工作区中的所有组件。
RealWorld 组件“抽屉”包含八个组件
环境
环境“environments”是存储所有环境的文件夹。每个环境都是一个可以在 IDE 或代码编辑器中打开的项目。我们可以将环境文件夹想象成一个“工作台”,它可以访问工作空间中的所有环境。
环境是与构件一起工作的地方
这给了我们选择多个环境的条件,每个环境都有不同的组件和基底。
接口
接口“interfaces”是存储所有“工作空间接口”的文件夹。“工作空间接口”保证构件的实现互不访问,实现组件之间的隔离。
系统
我们可以把系统“systems”文件夹想象成一组“架子”,它把工作区中的所有系统都保存起来。
系统将选定的构件组装成整个工件,通过构建和部署多个工件,从单体开发(monolithic 在一个环境中拥有所有构件)出发,实现水平可伸缩性的提升。
Polylith 工具优化 Polylith 系统的产生、发展、测试和建造。Polylith 工具帮助使用者:
- 创建工作空间、环境、系统、基础、组件和接口的结构
- 仅仅测试或构建上次成功后改变的构件
- 查看任何代码改变的状况
学习 Polylith 工具的 GitHub 库,了解它是如何工作的以及如何使用它。如果想快速浏览这个工具的所有特点,从命令部分开始是一个很好的选择。
Polylith 的优点
让我们把 Polylith 与之前看的三种结构进行比较。Polylith 系统可以作为单个工件(如单体)、作为生态系统中协作的多个工件(如微服务)部署,或者作为无服务器体系结构中的 Lambda 功能。
开发
Polylith 的单一开发环境允许我们在一个地方与所有的构建块一起工作。这将我们的开发经验与我们所选择的部署架构断开连接。
优点 | 解释 |
调试 | Polylith 开发环境中的所有代码都可以在单个 REPL 中运行,提供了一流的 REPL 驱动的开发和调试体验。 |
快速反馈 | Polylith 工具跟踪自上次成功测试或构建以来哪些基础和组件已经被更改,并且只进行编译和测试。这给我们在本地开发环境以及持续集成环境中构建和部署提供了闪电般的快速反馈。 |
重构 | 组件接口通过简单函数调用连接到它们的实现。这意味着可以用 IDE/ 代码编辑器安全地重构接口。 |
可重用 | 组件是可重用的,因为它们是封装的,无状态且可组合的。它们可以在单个系统和多个系统中重复使用。 |
简洁性 | Polylith 构件只是代码,是接口后面的函数集合。接口保证封装,这确保了代码库分离,保持更简单的系统。 |
易测性 | 组件的封装和功能性质使得它们作为完整的系统,易于被隔离测试 |
生产
Polylith 不仅允许延迟部署的决策,还允许我们在需要时轻松地改变部署的架构。这是因为 Polylith 可以将组件重组成任意数量的系统,这样就可以很容易地部署它们以满足性能需求。
优点 | 解释 |
成本 | Polylith 允许我们避免对解决方案的分布做出过早的设计决定。这使我们避免了“过早分发”,降低了部署复杂度并降低了运营成本。 |
部署 | Polylith 工具使得构建和部署过程在本地和在我们的 CI(持续集成)环境中都简单且无缝。 |
可伸缩性 | 当 Polylith 系统没有达到需要的性能 / 可伸缩性时,那么 Polylith 使得创建新系统和水平伸缩变得容易。可以重用每个新系统中的现有组件。 |
实际应用
六个实际应用中的 Polylith,所有服务的真实用户
这些项目的规模和复杂度,从一个完整的招聘平台到一个简单的 Web 应用程序。这些项目告诉我们,组件是一个强大的工具,可以完成复杂的、任何规模的系统。构建像乐高一样的系统比我们能想象的更快、更容易、更愉快!
迁移到 Polylith
从一个单体迁移到 Polylith,微服务或无服务器架构是相对容易的。这是因为我们可以单独地将每个人工制品迁移到 Polylith 系统,而不需要改变我们的部署。
从单体架构
不要忘记检查系统编译、构建和所有测试在每个步骤之后通过。
- 创建一个新的工作区,并添加一个新的系统,带有一个空的基底。
- 将整块代码,包括 API 和库复制到这个基底中。
- 从基底(除了 API)中提取所有代码为单个“monolith”组件。
- 重构代码来提高系统的质量。从我们的“monolith”组件一次一个组件开始:
一个有五个组成部分的系统
从微服务架构
微服务是一个由许多小单体组成的体系结构。这意味着迁移到 Polylith 就像在每个服务上执行单体迁移步骤一样简单:
从无服务架构
无服务器是一个由许多 Lambda 函数组成的体系结构。这意味着迁移到 Polylith 就像在每个 Lambda 上执行单体迁移步骤一样简单:
具有单个端点的 Lambda 函数
下一步
如果你想了解更多关于 Polylith:
- 如果您已经阅读了所有的文档,但是还没有掌握所有的概念,那么我们建议您观看视频。
- 如果您通过查看代码学习得最好,然后转向 RealWorld 示例应用程序。可以克隆这个项目并开始探索,或者只是浏览代码并阅读它的设计。
- 如果您想讨论 Polylith,那么来和我们一起在 Polylith 论坛上聊天。使用它作为一个空间来打招呼,给出反馈,问问题,分享您的 Polylith 经验.
Polylith 团队
感谢张婵对本文的审校。
评论 1 条评论