软件架构之原则、风格和实践

简介: 软件架构之原则、风格和实践

好的架构可以更容易的支持业务演进,更容易修改,容错性更好。而糟糕的架构就像一团浆糊,一点小小的改动可能都会对原有功能造成破坏。本文从很高层的角度介绍了什么是糟糕的架构,什么好的架构,以及构建好的架构的一些常用参考模型。原文:Software Architecture — Principles, Practices & Styles[1]


为解决特定的问题设计正确的架构更像是一门艺术而不是科学,其实践在很大程度上依赖于对问题的陈述、环境以及我们对问题的理解。对于任何架构来说,最重要的是在面对业务和规模需求变化时的适应性。下面是我关于将不同架构风格、原则和方法如何结合在一起,以形成一个可以演进的架构的经验。


什么是糟糕的架构?如何识别出糟糕的架构?


为了提高开发速度,开发人员经常会偷懒写一堆乱七八糟的代码,也就是我们通常所说的意大利面条式代码。这些代码经常会隐藏很多 bug,时不时造成功能瘫痪,而重新构建代码的成本甚至比修复现有代码的成本更低。这些代码包含下述特征:


  1. 不必要的复杂性(Unnecessarily Complex)——具有讽刺意味的是,编写复杂的代码很容易,任何人都能做到,但编写简单的代码却很困难。
  2. 刚性/脆弱性(Rigid/Brittle)——因为代码复杂,所以不容易理解,因此维护很困难,即使是很小的代码更改也很容易出错。
  3. 不可测试性(Untestable)——代码耦合紧密,通常不会遵循单一责任原则,难以测试。
  4. 不可维护性(Unmaintainable)——测试覆盖率较低的脆弱代码演变成维护的噩梦。


什么是好的架构?有什么特征?


  1. 简单(Simple)——容易理解。
  2. 模块化/分层/清晰(Modularity/Layering/Clarity)——这点很重要,对某个层进行修改不会影响到其他层,层与层之间的耦合最小。
  3. 灵活/可扩展(Flexible/Extendable)——可以很容易适应新的演进需求。
  4. 可测试/可维护(Testable/Maintainable)——易于测试,方便添加自动化测试,鼓励 TDD 文化,因此更容易维护。


为什么要重视架构、原则和实践?


降低成本(Cost reduction)——虽然最初的开发速度可能会降低,但最终,构建和维护的总成本会降低。


构建最重要的东西(Build what is essential)——我们需要构建最重要和必要的部分。只在必要的时候构建必要的东西,这一点很重要。这种方法通过只构建必要的内容,从而减少代码维护的开销,有助于清理混乱。


优化(Optimization)——优化以获得更好的可维护性,对于开发者和用户来说,优化应该提前完成。


性能优化(Performance Optimization)——在规划和设计可以为性能而演进的系统时,请记住,针对性能的代码级优化应该推迟到 LRT(Last Responsible Time,最后一刻)。


最后负责时间(Last Responsible Time)——LRT 是从精益原则中借鉴的概念,在精益原则中,决策/变更被推迟到某个时间点,超过这个时间点,不做决策的成本将比做决策的成本更高。当需求不够紧迫/重要的时候,设计决策应该推迟到 LRT,这样我们就有足够的知识来做出合理的设计决策。


适应性/进化(Adaptability/Evolution)——当软件不断适应业务和规模的新需求时,总是遵循进化模式。


我们可以使用什么工具、方法和技术?


  1. 精益原则(Lean principles)——构建正确的东西,构建必要的内容。
  2. 敏捷方法(Agile methodology)——建立正确的方法,以敏捷、适应性强、快速响应不断变化的市场需求的方式构建软件。
  3. 测试驱动开发实践和自动化测试(Test-driven development practice & Automated Tests)——测试驱动代码实现,确保可测试的软件设计,支持测试左移,“尽早测试,经常测试”可以帮助实现可维护的代码,消除对无意中破坏现有功能的恐惧。


遵循什么架构风格?


一般来说,没有一种方法是万能的。对于某个问题的设计决策取决于环境,每个设计都是权衡。下面是一些最常用的架构风格,只有需要做出设计决策的人才知道什么组合是最适合的。


  1. 以领域为中心的架构(Domain Centric Architecture)
  2. 以应用为中心的架构(Application Centric Architecture)
  3. Screaming Architecture
  4. 微服务架构(Microservices architecture)
  5. 事件驱动架构(Event-Driven Architecture — EDA)
  6. 命令查询职责分离(Command Query Responsibility Segregation — CQRS)


以领域为中心的架构(Domain Centric Architecture)


领域是模型的中心,其他一切都围绕领域构建的,应用层、表示层、持久层、通知服务、web 服务等等,也就是说,领域是基本的,其他一切都只是一个可替换的实现细节。


这里的域表示系统用户的心智模型,是架构中最稳定的部分,很少发生变化。接下来是嵌入用例的应用程序层,这些用例定义了其他一切。



image.png

图片来源:https://images.app.goo.gl/cW5QmNMxn912DM4D6


有两种领域模型:六边形模型和洋葱模型。本质上,每一个外层都依赖于内层,而内层不知道外层。


image.png

图片来源:https://images.app.goo.gl/dRgpzwPR9w8u5u4t7


优点


  1. 支持领域驱动设计(DDD,Domain-Driven Design)的思想,重点关注域、用户和用例。
  2. 减少域(稳定且更改较少)和实现细节(变更频繁,如表示层、数据库)之间的耦合。


缺点


  1. 初始成本更高,因为必须将更多的时间/思考/讨论用于划分领域与应用层所需的单独模型。
  2. 因为需要更多思考,所以开发人员不喜欢,他们坚持旧的以数据库为中心的三层架构。


以应用为中心的架构(Application Centric Architecture)


一旦定义了域边界,接下来就是应用层。通过应用下面的 SOLID 原则,应用层将更加健壮。


image.png

图片来源:https://images.app.goo.gl/UwBEyStMHaVVJt7u5


抽象(做什么?)——应用程序应该以一种能够通过抽象容纳业务逻辑的方式构建,专注于想做的事情。


解耦(怎么做?)——架构已经完成,实现细节使用依赖注入(DI,dependency injection)插入。依赖注入不仅适用于使用各种设计模式注入业务逻辑,而且尤其适用于注入基础设施元素,如数据库、缓存、通知服务器、外部 web 服务等。


接口/契约(交互)——这种方式自动构建了分层体系结构,每个外部元素都有清晰的接口。职责分离与每一层拥有单一职责相结合可以减少耦合,反过来有助于构建易于测试的代码,这些代码也可以使用 mock 进行单元测试。


同样,应用层不应依赖于任何其他实现细节,只了解它所依赖的领域层。


代码的功能性组织(Screaming Architecture)


体系架构应该突出系统的意图——Uncle Bob


建筑设计图很好的阐明了这一点,每个房间的用途都一目了然。


image.png

图片来源:https://images.app.goo.gl/3am8cnt6BrzFzh3JA


对于后端层——我们可以通过目录结构根据功能进行代码的模块化,具有功能内聚性的代码放在一起。每个模块都可以有一个聚合根作为模块的单一入口,因此只要查看聚合根,我们就能够写出模块的所有用例,从而简化模块的功能意图。


image.png

图片来源:https://levelup.gitconnected.com/let-me-hear-you-screaming-architecture-3adcc02f2ca3


对于表示层——可能仍然需要遵循模型/视图/控制器的旧分类方法。表示层应该保持轻量级,没有业务逻辑。这有两个好处:首先,消除重复的逻辑。其次,这样的组织可以帮助初级 UI 开发人员专注于丰富 UI。


微服务架构


过去,我们用统一的领域模型来表示销售上下文和支持上下文中的客户或产品。例如,支持联系人和销售客户被建模为单个 Customer 模型。随着解决方案空间变得越来越大,拥有越来越多的域,我们添加了更多的参数、属性和验证规则,有些规则只适用于一个域,而不适用于另一个域,从而导致统一代码的不必要的复杂性开销。


image.png

图片来源:https://images.app.goo.gl/HHfv3ojn17B5L1dU7


有界上下文(Bounded Context)——识别特定上下文范围,在该范围内,领域模型的特定术语是有效且有意义的。


在新模型中,不必在支持域的“Customer”中定义“Contact”,而是在支持域中使用正确的术语“Contact”。当与 Sales 域对话时,可以使用定义良好的接口将“Contact”转换为“Customer”对象。这样可以提高内聚性,减少跨不同领域的耦合。


微服务定义(Microservices defined)——将单个系统划分为子系统,子系统作为服务承担单一职责,通过明确定义的接口相互通信。每个服务可以自主部署,有自己独立的数据库和支持服务。每个微服务都是独立的,可以选择最适合自己的技术栈、工具和实践。每个服务可以独立缩放。负责每个服务的团队规模相对较小,一个团队可能负责一个或多个微服务。每个团队只需要了解他们负责的微服务的领域知识,而不需要知道整个系统的所有内容。


缺点

  1. 初始成本较高。
  2. 必须有 DevOps 自动化和自动化部署。
  3. 这种分布式计算架构在处理延迟、负载均衡、日志记录、监控、处理最终一致性等方面需要付出额外的时间和成本。

事件驱动架构(Event-Driven Architecture — EDA)


微服务可以通过 REST 调用的请求/响应机制(如 JSON)相互通信,或者使用带有消息代理的事件驱动架构。现代体系架构更喜欢 EDA,这样服务的响应更快、延迟更少,可以提供更健壮、可容错、有保障的服务,并允许更好的可伸缩性。


在 EDA 中有三个参与者,即创建触发事件的生产者、以健壮的方式携带消息的消息代理和可以订阅特定/所有事件的消费者,这些构成了“反应式编程(Reactive Programing)”。这种模式对来自数据流的事件(触发器)做出反应,从而获得更快的响应时间和更低的延迟。


对于需要支持跨微服务事务最终一致性,具有 ACID 属性的微服务,可以使用 SAGA 模式,其支持显式回滚机制来处理错误回滚。不过这种设计会变得更复杂,应该谨慎使用。


EDA 还定义了事件源(Event Sourcing),作为将数据存储在 DB 中的新机制。在这里,DB 对象永远不会被更新。相反,要获取对象的当前状态,需要按照事件到达的顺序进行处理。在事件源中,通过以固定的时间间隔创建当前状态的快照来实现性能优化。

CQRS 模式-命令查询职责分离


微服务和 EDA 的出现也催生了 CQRS 模式,其中“命令”用于修改底层对象的状态,“查询”不修改对象,只是返回请求的对象子集。

这有什么用?以下是一些示例。
  1. 可以在不影响写的情况下提高读的可伸缩性。例如,通过在 MongoDB 中添加更多的辅助节点来满足读取需求,可以选择性地扩展读取能力。
  2. “命令”必须将更新请求发送到数据库,可以选择使用缓存来提供更快的读取。
  3. 有时对象可能属于另一个微服务,而每次查询另一个微服务的开销可能很大,因此可以使用缓存来满足查询需求。数据虽然有重复,但只要维护好数据,并且变化不是很快,就能够在很大程度上减少延迟。这样同时也提高了可用性,即使在其他微服务不可用的情况下,我们的微服务也可以继续正常工作。例如:在订单服务中缓存产品目录。


以上是一些常用的高层设计选择和实践,这些可以和其他一些底层设计(不同设计模式、原则、工具等的组合)一起使用的。所有这些都以一种有意义的方式组合在一起,从而定义一个敏捷的、适应性强的、可扩展的、可维护的、可测试的,当然最重要的是简单的解决方案。



References:

[1] https://sarada-sastri.medium.com/software-architecture-principles-practices-styles-a0263aa11530


目录
相关文章
|
10天前
|
搜索推荐 NoSQL Java
微服务架构设计与实践:用Spring Cloud实现抖音的推荐系统
本文基于Spring Cloud实现了一个简化的抖音推荐系统,涵盖用户行为管理、视频资源管理、个性化推荐和实时数据处理四大核心功能。通过Eureka进行服务注册与发现,使用Feign实现服务间调用,并借助Redis缓存用户画像,Kafka传递用户行为数据。文章详细介绍了项目搭建、服务创建及配置过程,包括用户服务、视频服务、推荐服务和数据处理服务的开发步骤。最后,通过业务测试验证了系统的功能,并引入Resilience4j实现服务降级,确保系统在部分服务故障时仍能正常运行。此示例旨在帮助读者理解微服务架构的设计思路与实践方法。
58 16
|
11天前
|
存储 消息中间件 小程序
转转平台IM系统架构设计与实践(一):整体架构设计
本文描述了转转IM为整个平台提供的支撑能力,给出了系统的整体架构设计,分析了系统架构的特性。
53 10
|
1月前
|
弹性计算 Java 关系型数据库
Web应用上云经典架构实践教学
Web应用上云经典架构实践教学
Web应用上云经典架构实践教学
|
18天前
|
负载均衡 Serverless 持续交付
云端问道9期实践教学-省心省钱的云上Serverless高可用架构
详细介绍了云上Serverless高可用架构的一键部署流程
46 10
|
18天前
|
存储 人工智能 运维
面向AI的服务器计算软硬件架构实践和创新
阿里云在新一代通用计算服务器设计中,针对处理器核心数迅速增长(2024年超100核)、超多核心带来的业务和硬件挑战、网络IO与CPU性能增速不匹配、服务器物理机型复杂等问题,推出了磐久F系列通用计算服务器。该系列服务器采用单路设计减少爆炸半径,优化散热支持600瓦TDP,并实现CIPU节点比例灵活配比及部件模块化可插拔设计,提升运维效率和客户响应速度。此外,还介绍了面向AI的服务器架构挑战与软硬件结合创新,包括内存墙问题、板级工程能力挑战以及AI Infra 2.0服务器的开放架构特点。最后,探讨了大模型高效推理中的显存优化和量化压缩技术,旨在降低部署成本并提高系统效率。
|
20天前
|
运维 监控 安全
天财商龙:云上卓越架构治理实践
天财商龙成立于1998年,专注于为餐饮企业提供信息化解决方案,涵盖点餐、收银、供应链和会员系统等。自2013年起逐步实现业务上云,与阿里云合作至今已十年。通过采用阿里云的WA体系,公司在账号管理、安全保障、监控体系和成本管控等方面进行了全面优化,提升了业务稳定性与安全性,并实现了显著的成本节约。未来,公司将持续探索智能化和全球化发展,进一步提升餐饮行业的数字化水平。
|
20天前
|
运维 安全 架构师
架构师工具箱:Well-Architected云治理提效实践
本次分享基于阿里云Well-Architected Framework的最佳实践案例,涵盖企业从上云到优化的全过程。安畅作为国内领先的云管理服务提供商(Cloud MSP),拥有800多名员工,其中70%为技术工程师,为企业提供架构安全、数据智能等技术服务。内容包括Landing Zone与Well-Architected的关系、企业云治理现状及需求分析,重点探讨了安全合规、成本优化、资源稳定性和效率提升等方面的最佳实践,并通过具体客户案例展示了如何通过自动化工具和定制化解决方案帮助企业提升云上业务价值。
|
1月前
|
消息中间件 运维 安全
后端开发中的微服务架构实践与挑战####
在数字化转型的浪潮中,微服务架构凭借其高度的灵活性和可扩展性,成为众多企业重构后端系统的首选方案。本文将深入探讨微服务的核心概念、设计原则、关键技术选型及在实际项目实施过程中面临的挑战与解决方案,旨在为开发者提供一套实用的微服务架构落地指南。我们将从理论框架出发,逐步深入至技术细节,最终通过案例分析,揭示如何在复杂业务场景下有效应用微服务,提升系统的整体性能与稳定性。 ####
50 1
|
1月前
|
消息中间件 运维 API
后端开发中的微服务架构实践####
本文深入探讨了微服务架构在后端开发中的应用,从其定义、优势到实际案例分析,全面解析了如何有效实施微服务以提升系统的可维护性、扩展性和灵活性。不同于传统摘要的概述性质,本摘要旨在激发读者对微服务架构深度探索的兴趣,通过提出问题而非直接给出答案的方式,引导读者深入
49 1
|
1月前
|
Cloud Native API 持续交付
云原生架构下的微服务治理策略与实践####
本文旨在探讨云原生环境下微服务架构的治理策略,通过分析当前面临的挑战,提出一系列实用的解决方案。我们将深入讨论如何利用容器化、服务网格(Service Mesh)等先进技术手段,提升微服务系统的可管理性、可扩展性和容错能力。此外,还将分享一些来自一线项目的经验教训,帮助读者更好地理解和应用这些理论到实际工作中去。 ####
52 0

热门文章

最新文章