第 16 章 松耦合架构与微服务
有趣的是,软件架构对软件交付性能的影响比你构建的系统类型更大。无论你的产品是云服务、运行在制造硬件上的嵌入式软件、消费者应用、企业应用,甚至是主机软件,架构的某些特征对工程性能几乎没有影响(Forsgren N.、Humble J. 和 Kim G.,2018)。对于每种系统类型,都存在高性能和低性能的情况。然而,架构的特征与工程速度之间显然是相关的,它使得架构成为提升性能的关键加速因素。
在本章中,我将为你概述松耦合系统,以及如何通过演化软件和系统设计来实现高工程速度。
本章将涵盖以下主题:
-
松耦合系统
-
微服务
-
演化设计
-
基于事件的架构
松耦合系统
所有曾经在紧耦合的单体应用上工作的开发人员都知道它所带来的问题。通信开销大,进行更大改动时需要召开大量会议。修复某个部分的bug后,常常会在其他地方引入新bug。改动可能会破坏其他开发人员的功能。这些问题导致开发人员对集成和部署产生恐惧,从而减慢了开发速度。
在设计系统和软件时,你应该关注以下几个特性:
-
可部署性:每个团队是否能够独立发布他们的应用,而不依赖于其他应用或团队?
-
可测试性:每个团队是否能够在没有需要多个团队独立解决方案部署到同一测试环境中的情况下,进行大部分的测试?
这里的团队规模是一个典型的“两块披萨”团队(见第17章《赋能你的团队》)。如果你为小型团队的可部署性和可测试性设计系统,它将自动导致松耦合系统的出现,并且这些系统会有明确的接口。
微服务
松耦合系统最常见的架构模式是微服务模式,"一种将单个应用程序开发为一组小型服务的方式,每个服务运行在自己的进程中,并通过轻量级机制进行通信,通常是HTTP资源API"(Lewis J. & Fowler M., 2014)。
微服务源自面向服务架构(SOA),并具有一些额外的特点。微服务具有去中心化的数据管理——意味着每个服务完全拥有自己的数据。此外,微服务倾向于使用轻量级的消息传递机制,而不是复杂的协议或中央协调进行服务间通信——智能端点和愚蠢的管道。
微服务的一个重要特征常常被忽略——它们是围绕业务能力构建的。这也决定了服务的规模。要定义服务的范围,你必须理解业务领域。一个微服务对应领域驱动设计中的一个界限上下文(Eric Evans, 2003)。
另一个特征是,微服务是完全独立可部署和可测试的。这就是为什么它们与高工程速度相关联的原因。
微服务有许多优势。它们的扩展性非常好,因为你可以独立扩展每个服务。它们还允许每个团队使用最适合其需求的编程语言和数据存储解决方案。最重要的是,它们使得在大型复杂应用中,团队可以快速推进而不打扰其他团队。
但是,这些优势是有代价的。基于微服务的应用程序非常复杂,且难以操作和排除故障。
有许多著名的基于微服务的解决方案——例如Netflix和Amazon。它们运行全球规模的服务,并且具有能够让它们每天部署成千上万次的架构。
但也有许多公司尝试实施微服务却失败了。尤其是绿地项目失败的比例很高。原因往往是对业务领域缺乏了解,未能正确界定每个服务的边界上下文,尤其是在应用由外部公司开发并且还未学习领域的通用语言时。另一个原因是低估了操作服务的复杂性。
因此,在实施微服务之前,你应该关注架构的可部署性和可测试性特征,并根据你的需求调整解决方案设计。需求是不断变化的,架构也应该随着时间的发展而演进。
进化设计
某些架构风格的优缺点会因各种原因而发生变化。其中一个原因是应用程序的规模。另一个原因是对业务领域和客户的了解以及操作规模的能力。根据这些因素,不同的架构风格更适合你(见图16.1):

不断地将架构和系统设计适应当前的需求被称为演进设计。对于一个绿地产品,最好的起点是采用单体架构和一个团队。这可以让你在没有太多开销的情况下快速推进。如果你扩展规模并且对业务领域有更多了解,你可以开始使用编程语言的能力对应用进行模块化。到了某个时刻,复杂性和规模会变得如此之高,以至于微服务可以帮助你保持产品的可测试性和可部署性。
问题是——你如何从现有架构走向所需的架构?完全重写是非常昂贵且具有风险的。更好的方法是演化你的设计。Martin Fowler称这种方式为“攀缘无花果应用模式”(Strangler Fig Application)(Martin Fowler, 2004)。攀缘无花果是一种植物,它的种子首先在树的上部枝干上发芽,逐渐将根部向下生长,直到最终扎根于土壤中。支持它的树木被慢慢“勒死”,最终死亡——留下一个自给自足的有机结构。
与其重写应用,不如围绕现有系统生长一个新的“攀缘无花果”应用,逐渐让它成长,直到旧系统被“勒死”并能够关闭。
事件驱动架构
除了微服务、单体应用和多层应用外,还有其他架构风格——例如,事件驱动架构(EDA)。EDA 是一种围绕事件的发布、处理和持久化的模式。其核心是消息代理——例如 Apache Kafka——各个服务或组件可以发布事件(发布者)或订阅事件(订阅者)。
EDA 可以与基于微服务的方法很好地结合使用——但它也可以与其他架构风格配合使用。它可以帮助你在松耦合的组件或服务之间保持一致性,并且由于事件的异步特性,它能够完美地水平扩展,因此非常适合处理大量流动数据的解决方案,如近实时处理传感器数据的物联网解决方案。
特别是在云原生环境中,EDA 可以帮助你快速构建松耦合和全球可扩展的解决方案,并且在短时间内实现。
与 EDA 经常一起使用的一个模式是事件溯源(Event Sourcing)。与其持久化实体,事件溯源将所有应用状态的变化——包括实体——作为一系列事件进行捕捉(见 Martin Fowler,2005)。为了检索实体,应用程序必须回放所有事件,以获取最新的状态。由于事件是不可变的,这提供了一个完美的审计追踪。你可以将事件流视为不可变的事实流,作为单一的真实来源。除了可审计性外,事件溯源在可扩展性和可测试性方面也有许多优点。
事件溯源适用于需要捕捉数据意图、目的或原因的场景,尤其是在必须避免冲突更新、需要保持历史记录并频繁回滚更改时。事件溯源与命令查询责任分离(CQRS)模式非常配合——CQRS 是一种将读操作和写操作分开的模式。
但需要注意的是,事件溯源是非常复杂的,且大多数开发人员不太容易自然地将领域建模为事件。如果上述条件不适用于你的产品,那么事件溯源可能并不是一个合适的模式。
对于简单领域,另一个更适合的架构风格是 Web-Queue-Worker。这是一种主要用于无服务器 PaaS 组件的模式,由一个处理客户端请求的 Web 前端和一个在后台执行长时间运行任务的工作者组成。前端和后端是无状态的,通过消息队列进行通信。该模式通常与其他云服务结合使用,如身份提供者、数据库、Redis 缓存和 CDN。Web-Queue-Worker 是开始构建云原生应用的一个不错的模式。
无论你选择哪种架构风格,都要尽可能保持简单。最好从简单的设计开始,并随着需求的增加逐步演化设计,而不是过度设计,最终导致一个复杂的解决方案,反而拖慢了开发速度。
总结
如果你正在采用 CI/CD 和 DevOps 实践,但没有加速,那么你应该仔细检查你的解决方案架构,因为它是工程速度的关键指标之一。应该关注部署性和可测试性的特性,而不是关注架构风格。
在本章中,我向你概述了松耦合系统的演进设计,并介绍了一些相关的架构风格和模式。
在下一章,我们将讨论组织结构与软件架构之间的关联,以及它们如何在 GitHub 中结合起来。
进一步阅读
以下是本章中提到的参考资料,您可以通过这些资源获取更多相关主题的信息:
-
Forsgren N., Humble, J., 和 Kim, G. (2018). Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations (第1版) [电子书]. IT Revolution Press.
-
Lewis J. 和 Fowler M. (2014). Microservices: https://martinfowler.com/articles/microservices.html.
-
Eric Evans (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley Professional.
-
Martin Fowler (2004). StranglerFigApplication: https://martinfowler.com/bliki/StranglerFigApplication.html.
-
Michael T. Nygard (2017). Release It!: Design and Deploy Production-Ready Software. Pragmatic Programmers.
-
Martin Fowler (2005). Event Sourcing: https://martinfowler.com/eaaDev/EventSourcing.html.
-
Lucas Krause (2015). Microservices: Patterns and Applications – Designing fine-grained services by applying patterns [Kindle版].