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