软件架构是一门需要经验和广泛技术知识的艺术。遗憾的是,世上并不存在万能的架构解决方案,适用于所有情况。在设计软件架构时,必须根据特定应用的需求和团队情况进行量身定制。需要考虑的因素可能包括:
性能、可扩展性、弹性、灵活性、简洁性、可靠性、成本、团队技术能力……
模式是任何软件架构的基石。幸运的是,有许多可以组合使用的不同模式,取决于你的需求。选择正确的模式通常取决于你愿意为符合你的需求而做出的取舍。在开始软件架构的旅程之前,首先要了解哪些模式在特定场景下效果较佳。本文将介绍一些我最喜欢的模式及其优势。
我首先要介绍的架构模式其实并不算是传统意义上的模式。然而,有界上下文是构建可维护代码的最有力的方法之一。该概念源于领域驱动设计(DDD)的战略设计,但如今我看到它被应用于许多不使用 DDD 的项目中。
有界上下文用于表示应用中概念的明确逻辑边界。它们代表了一个应用的特定领域模型在使用的区域,并在该区域中术语、规则和数据表示保持一致和连贯。
例如电商应用中的概念可以按照有界上下文进行划分:目录、订单、支付和身份管理。
有界上下文有助于在应用概念数量随着时间增加时抵消紧密耦合和复杂性。
我还发现按有界上下文组织项目中的代码非常有用,特别是在你计划未来将代码分解为模块或微服务时会更为便利。
以下是使用清晰架构的同一电商应用的示例。这种方法也称为领域中心架构。
垂直切片架构目前变得非常流行。它将这一概念进一步发展,将整个应用划分为垂直的部分。在此过程中,了解有界上下文至关重要。
与清晰架构中的分层方式不同,有界上下文被分割为独立的垂直切片。
事件风暴是为你的有界上下文绘制地图的一个很好的方法: Why You Should Be Using Event Storming.
我已经在团队中使用事件风暴一段时间了,到目前为止,它被证明是最强大的解决方案设计和发现工具。如果你正在使用领域驱动设计或试图建模一个特别复杂的问题,事件风暴特别强大!
-
中到高领域复杂度
-
预期功能或概念数量随时间增加的应用
-
应用可能在未来被分成独立的应用/部署
边车是一种在同一主机上运行的另一应用前端应用。主机可以是服务器、虚拟机、容器或 Kubernetes pod。边车通常用于拦截到下游应用的数据流。
边车可以用来在不修改下游应用的情况下透明地扩展其功能。最典型的边车使用场景是在下游应用上增加额外的安全功能,例如认证、授权或网络策略,它们在下游应用中并不存在。
边车还可以用来以标准化的方式添加跨应用通用的关切点。以下是一些示例:
-
日志记录
-
性能监测
-
跟踪
-
认证与授权
-
重试策略及断路器
-
透明加密
服务网格将边车的概念扩展至极限,通过为每一个集群的应用配对一个边车代理。所有应用到应用的通信都是通过边车完成的。这允许通过控制平面对所有流量进行加密、监控和安全管理。
我的团队在所有 Kubernetes 集群上使用服务网格,以便能够以标准化的方式确保和监控它们。
-
应用无法修改但需要额外的跨应用特性
-
需要在多个应用之间集中化或标准化的跨应用关切点
这种模式用于通过消息代理促进发布者与订阅者之间的_异步_通信。消息代理包含一个或多个主题,可以进行发布和订阅。
消息在主题中排队,并以先进先出(FIFO)的方式分发到下游的订阅者。这种模式可用于在连接的应用之间集成数据或事件。也可以用于安排长时间运行的任务异步执行在一个工作服务中。
发布-订阅模式有两种主要传递机制:
-
竞争消费者:每条发布的消息仅由_任何一个_订阅者消费一次。此模式可用于通过多个订阅者工作服务来并行扩展长时间运行的任务处理。
-
扇出:每条发布的消息由_所有_订阅者消费。此模式通常用于跨多个下游应用同步事件或数据。
-
需要异步通信
-
扩展和负载均衡长时间运行的任务
-
在多个下游应用之间集成数据更改或事件
应用网关与负载均衡器的功能类似,通过路由网络流量到多个下游应用。负载均衡器在第 3/4 层(使用端口/IP 地址)上操作,而应用网关在第 7 层上操作。这使得应用网关可以执行深度数据包检测,以便基于应用信息(如请求_主机名、路径以及头信息_ )实现路由。
应用网关最常见的路由场景是_基于路径_。如果组织有许多不同的后端服务(如面向服务或微服务架构)需要托管,则使用应用网关可以通过在单个 URL 后面提供所有服务来简化维护。应用网关通常还提供额外的安全特性,例如 SSL 终止或网络应用防火墙(WAF)。
我们团队构建的基础设施平台中应用网关扮演了关键角色,用于托管我们所有的产品: 我们是如何在 Kubernetes 之上构建基础设施平台.
我们的应用网关使我们能够在相同的 URL 上托管许多不同的租户产品,从而将需要设置和维护的 SSL 证书和 DNS 注册数量降至最低。
-
需要在同一个 URL 或 URL 集后托管多个应用
-
使用路径前缀或主机名进行下游服务的路由
微服务用于将应用程序划分为小的独立服务。每个微服务必须拥有专用的 API 和数据库。微服务可以采用不同的底层技术,并且应能够独立部署和扩展。
微服务常常使用单一的整体前端和应用网关,通过一个 URL 来服务所有流量。
微服务可以极其强大和灵活,但同时会增加巨大的复杂性。如果你打算使用微服务,那么请确保有充分的理由来接受所有这些复杂性。
-
应用的不同部分对性能、扩展或可用性有不同要求
-
应用的不同部分需要不同的数据库技术
-
应用的不同部分需要独立部署
-
大型开发团队可以分成更小的高效团队,独立开发微服务
微前端尽管微服务常用于拆分后端应用程序,但微前端则用于拆分网页前端。一个前端可以由多个不同的小型微前端构建,这些微前端各自独立托管。这意味着可以在隔离状态下开发和发布功能,涉及到后端微服务和网页微前端组件。
每个微前端必须在前端网页应用程序中拥有自己的独立界面。通常,会使用某种门户包装 UI 来渲染各种微前端。
大多数网页框架都有支持微前端的方式。最近,这些框架标准化地使用 Web Components。Web组件被包装成单一的JavaScript文件,这些文件允许UI组件通过自定义HTML元素在网页应用程序中托管。Web组件是完全封装的,这意味着每个微前端甚至可以在需要时使用不同的JavaScript框架。
-
与微服务有相同的需求
-
不同的团队负责维护和部署网页应用程序UI的不同部分
-
网页应用程序UI的不同部分必须可以独立部署
微服务允许每个服务使用不同的数据库技术;但如果每个微服务也需要使用不同的数据库技术该怎么办呢?CQRS允许使用不同的技术进行数据写入(写入侧)和数据读取(读取侧)。这可能意味着相同数据的不同表示,甚至可能完全使用不同的数据库技术。这个思想是每一个侧面都可以针对其承担的职责和期望的使用模式进行优化。
您可能希望在写入侧使用类似于S3存储桶这样简单便宜的东西,而在读取侧使用类似Elastic Search这样查询支持更好的工具。关系型SQL数据库可能更适合一侧,NoSQL数据库则适合另一侧。根据数据访问模式,我们还可以完全独立地扩展每一侧。
如果使用不同的数据库,那么通常会使用消息代理来通过最终一致性集成写入侧和读取侧之间的数据更改。
CQRS是一个复杂的模式,理解和维护都不简单;如果您打算使用它,请确保您的应用程序确实需要承担这个额外的复杂性。
CQRS有很多种不同的风格,复杂程度各异:选择适合您的CQRS架构。您可以根据特定需求选择一种方案。
事件溯源是我最喜欢的一种CQRS形式。它不是存储模型的当前状态,而是使用仅追加的事件存储来记录模型上所采取的完整操作序列。当发生新的命令时,通过重放该实例曾经经历的所有事件来"补水"模型/实体的当前状态。
为了帮助人们理解,通常会将事件溯源比作银行账户。所有的交易作为事件存储起来,通过重放所有的交易来计算余额。
写入侧的每个模型实例都作为其自身的独立事件流存储。事件流可以在任何时候重放,以物化不同的数据视图。如果读取侧不同步,我们可以查询写入侧的所有事件并重建我们的模型。
-
极高的性能和负载要求
-
读写侧需要不同的技术
-
需要事件溯源以增强审计能力
所有这些模式甚至可以在您的解决方案架构中整合,并与每个受限上下文的特定需求匹配。
以下是如何使用所讨论的模式架构相同的电子商务应用的另一个示例。
这种架构引入了最符合每个受限上下文需求的最佳技术和模式,这被称为多语式架构。
每个受限上下文都有自己的微服务和微前端。所有微服务通过应用程序网关在一个通用URL后面提供服务。
目录使用Redis优化查询性能。订单使用PostgreSQL实现强关系一致性。支付使用CQRS和事件溯源对复杂的支付流程进行建模。
大多数微服务都有自己的消息代理主题来发布事件。还有多个协调服务消费事件以集成其他受限上下文的数据更改和异步处理长时间运行的任务。
身份微服务使用KeyCloak进行身份验证。此外,还有一个通用的身份伴随程序实现,用于在其它微服务中以标准方式强制执行身份验证和授权。
这里展示的不同架构模式在许多不同的场景中都是强大的,但在某些情况下可能也过于繁琐。记住,始终以项目需求和团队能力为指导。了解哪些模式在特定情况下表现出色是设计最佳架构的关键!