Press "Enter" to skip to content

为什么你需要构建模块化单体应用

近年来,我们见证了使用微服务架构构建的应用程序数量显著增加。我们选择这种方法主要是因为小团队可以独立工作,互不干扰。然而,这是一个组织问题,而不是技术问题。我们还可以使用不同的技术构建每个服务,并实现独立扩展。

使用微服务方法也存在一些不足之处。系统变得复杂起来,维护和诊断问题(日志和追踪)变得非常重要,特别是在处理微服务时。然而,我们也看到过所谓的“微服务膨胀现象”,即使在Twitter上也存在这种情况。

但是,还有许多示例表明微服务方法失败了;如果你不是在解决像Netflix这样的大规模问题,那么你可能不需要微服务。

另一方面,我们对单体应用也有很多负面评价。但是,构建单体应用并不意味着更好。近年来,我们经常看到将单体应用与糟糕的架构或纯粹构建遗留代码等联系起来,但这并不是绝对的。是的,单体应用无法独立扩展或发布系统的独立部分,但这些主要是最大的缺点。然而,你仍然可以创建出出色的和高质量的代码。单体应用减少了复杂性,减少了网络调用次数,提供了更详细的日志记录等。整个应用程序或系统的大多数子系统存储在单体应用程序中。它被称为“自包含”,因为每个系统组件都存储在一个容器中。

我们可以构建具有全面用例和所需架构属性的架构单元,并避免处理微服务架构的复杂性。Shopify是最好的例子,拥有超过300万行代码,是世界上最大的单体应用之一。Shopify选择了模块化作为解决方案,在黑色星期五期间每秒处理1.27百万个请求。但还有更多的单体应用的例子,比如StackOverflowBasecampIstio。而且最近,我们还看到亚马逊的一个团队(Prime Video)放弃了微服务架构,转而选择单体应用

我们希望有单独的模块,并在其上工作,但同时保持简单,构建一个模块化的单体应用。一个适当构建的模块化单体应用明天可以成为微服务解决方案。因此,推荐的路径是:单体应用 > 应用程序 > 服务 > 微服务

“即使您确定您的应用程序足够大而值得这样做,也不应该使用微服务来启动新项目”,—— 马丁·福勒。

当我们想要构建模块化的单体应用时,在将其整合到单体应用程序中部署之前,将系统分为可管理的模块是至关重要的。由于将来的模块之间的所有通信可能会导致跨网络调用,因此在这种情况下,高内聚性和低耦合性非常重要。这意味着所有模块间的通信必须是抽象的、异步的或基于消息的,以便在未来的网络调用中处理。

我们如何实现这样的概念呢?首先,我们创建单独的模块,每个模块都有自己的架构,然后将这些模块集成到一个单一的API网关中。这样我们就可以将整个系统作为单体应用程序部署,但如果将来需要,我们也可以将单独的模块拆分为服务。

什么是分布式单体应用?

有三种类型的单体应用:

  1. 传统单体应用 这是最常见的单体应用类型,其中所有内容都被捆绑在一起。通常,我们在单个层和部署中具有一些用户界面、业务逻辑和数据访问层。领域之间没有明确的边界,代码会有共享库。
  2. 模块化单体应用 对于模块化单体应用,我们定义了精确的功能切片和依赖关系,这意味着我们可以使每个模块与其他模块独立。然而,仍然存在单一的部署单元和单一的数据库。这是我们想要实现的目标。
  3. 分布式单体应用 这是另一种现代单体应用变体,它的部署方式类似于微服务架构,但构建时考虑了单体应用的原则。通常,我们在尝试创建微服务架构时会不考虑一些需要的架构和流程变化。例如,当某些服务无法单独部署、过于啰嗦、无法扩展且共享相同的数据源时,我们就知道我们有了一个分布式单体应用。 这是一个明显的反模式示例。这些系统的构建方式类似于单体应用,但部署方式类似于微服务。这样做既有了紧耦合微服务的复杂性,又有了单体应用的复杂性。不幸的是,我们通常通过错误的方式开发微服务架构,导致这种情况的发生。

单体分解策略

如果我们陷入传统或分布式单体应用中,我们需要进行一些单体应用分解。有几种方法可以帮助解决这个问题:

  1. 封藤图模式 封藤图模式(由马丁·福勒提出)来自一组通过“勒住”寄主植物生长的植物。这种模式使我们能够用新的服务替换特定功能。在这种情况下,我们创建了一个拦截并将请求传递给单体应用或新服务的门面,并逐渐将旧功能迁移到新的服务中,而消费者始终与门面进行交互。 在这里,您还可以使用领域驱动设计(DDD)逐步将应用程序重构为更小的服务,首先在所有相关利益相关者之间找到广泛使用的语言(共同词汇),然后确定要将此词汇应用于的相关模块,并定义单体应用的领域模型。在最后一步中,您为这些模型定义了有界上下文,这是一个领域内的边界。
  2. 分支抽象 采用这种方法,我们在原始组件上创建了一个抽象层,以便可以逐步替换它。客户端的请求会定向到该层,允许我们在其后的所有内容上进行更改。当我们完成更改时,客户端只会访问新组件。使用这种模式,我们可以并行存在相同功能的两个实现,真正遵循里氏替换原则。尽管如此,与封藤图模式类似,这种模式在更低层次的抽象上工作,更关注于组件而不是系统。

如果您对这种方法感兴趣,我推荐阅读以下书籍:Sam Newman的“从单体应用到微服务”。

内容由GeekAI网页翻译服务自动翻译完成。 原文地址:https://newsletter.techworld-with-milan.com/p/why-you-should-build-a-modular-monolith

发表回复