购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第1章
没有最佳实践会怎么样

软件架构师等技术专家为什么要在大会上演讲或者写书?那是因为他们找到了所谓的“最佳实践”。虽然这个词已经被滥用到了光是提起它就会引起人们的抵触,但忽略这个词本身,技术专家写书是因为他们找到了新的方式来解决某个通用问题,希望借此传播给更广泛的受众。

但是如果大量的问题根本没有好的解决方案呢?软件架构中的所有问题其实都没有好的解决方案,有的只是针对一团乱麻的问题集合做出的(几乎)同样混乱的权衡取舍集合。

对于上网搜寻当前问题的解决方案来说,软件开发者个个技艺高超。例如,需要了解如何在环境中配置某个特定的工具时,只需熟练使用谷歌就可以找到答案。

但是这不适用于架构师。

对于架构师来说,很多问题带来的挑战都是独一无二的,这些挑战与组织中特定的环境和情况息息相关。

架构师可能疑惑过,与讨论框架、API等技术话题的书相比,为什么关于架构的书如此之少。架构师很少遇到通用问题,他们持续地在各种新奇场景中艰难地做着抉择。对于架构师来说,问题就像雪花,没有两片雪花是相同的。在很多情况下,问题不仅对于这个组织是新的,而且对于整个世界来说都是新的。没有任何已有的书籍或者会议讨论过这些问题!

架构师不应该盲目地为这些问题寻找银弹,它们和Fred Brooks在1986年创造这个词时一样稀少:

未来十年之内,无论是在技术上还是管理上,都不会有能将生产力、可靠性或简洁性提高一个数量级的单一的重大突破。

——Fred Brooks,“No Silver Bullet”

由于几乎每个问题都提出了新的挑战,因此架构师真正的工作在于他们能够客观地确定和评估相应决策每一方的权衡取舍,以尽可能好地解决问题。本书不讨论“最佳解决方案”,因为“最佳”暗示架构师成功地在设计中最大化了所有可能相互矛盾的因素。相反,我们的建议有点像开玩笑:

不要尝试在软件架构中寻求最佳设计,而应力求寻找不那么糟糕的取舍组合。

通常,架构师能做出的最好设计就是不那么糟糕的权衡取舍集合:没有哪个单独的架构特征能一骑绝尘,唯有平衡所有矛盾的架构特征才能让项目走向成功。

这也引出了一个问题:“架构师应该如何寻求这个权衡取舍集合(并有效地把它们记录下来)?”本书主要讲的是决策制定,帮助架构师在遇到全新的情况时做出更好的决策。

1.1 何为“难点”

本书的英文书名为 Software Architecture: The Hard Parts 。事实上,书名中的hard有两重含义。首先,它使人想到困难,架构师持续面对前所未有的困难问题,这涉及许多具有长期影响的技术决策,这些决策必须考虑人际关系和政治环境。

其次,hard也让人想到坚硬——正如有硬件和软件之分。硬的东西理应有更少的变化,因为它要作为软的东西的基石。类似地,架构师谈论架构和设计的区别,前者是结构上的,而后者更容易改变。因此在本书中,我们讨论的是架构的基础部分。

软件架构定义本身就已经让参与者贡献了数小时毫无成效的对话。我们最喜欢的一个定义甚至有点讽刺意味——“软件架构就是以后不好改的那些东西”。而本书就是关于“那些东西”的。

1.2 永不过时的软件架构建议

软件开发生态系统持续且混乱地转变和发展。几年前风靡一时的话题不是被生态系统吞噬掉,就是消失了,或是被不同的或更好的东西取代了。例如,10年前盛行的大型企业级架构风格是编排驱动、面向服务的架构,现在基本上没人再用这种架构风格了(稍后将解释其原因),现在备受分布式系统青睐的是微服务。这种转变是何时因何发生的?

当架构师审视一种特定的风格(尤其是过去的风格),必须考虑让这种架构走向主导地位的那些限制。彼时,很多公司正在合并成为大型企业,伴随转型而来的是整合问题。

此外,开源对大公司来说还不是可选项(通常是政治原因而不是技术问题)。因此,架构师强调共享资源和集中编排作为一种解决方案。

不过在这几年间,开源和Linux成为可行的选项,让操作系统在商用上免费。然而,真正的转折点发生于Linux在运行上变得更加自由灵活,随着Puppet和Chef这样的工具诞生,开发团队可以把启动环境作为自动化构建的一部分。一旦具备这项能力,便引发了架构变革,包括微服务和快速涌现的容器化基础设施,以及Kubernetes等编排工具。

这说明软件开发生态系统在以完全不可预料的方式进行扩展和演进。一个新功能导致另外一个新功能,你不知道它会创造出什么新的功能。随着时间推移,这个生态系统一点一点地把自己完全置换掉了。

这给技术(特别是软件架构)书籍作者提出了一个问题——如何让我们写的内容不会马上过时?

我们在本书中不关注技术和实现细节,而是重点关注决策制定和其中的权衡。我们使用当下的场景和例子来提供细节和上下文,但基本原则着眼于在遇到新问题时的权衡分析和决策制定。

1.3 数据在架构中的重要性

数据很珍贵,它比系统本身存在得更久。

——Tim Berners-Lee

对于架构来说,数据就是一切。每一家企业打造的任何系统都必须和数据打交道,因为数据往往比系统和架构存在得更久,需要下功夫去思考和设计。然而,在现代分布式架构中,很多数据架构师下意识地构建出紧密耦合的系统,从而激发了矛盾。例如,架构师和DBA必须保证业务型数据能够在单体系统解构后存活,无论架构如何变更,业务人员应该能持续从数据中获益。

都说数据是一家公司最重要的资产。业务人员想要从数据中提取价值,寻找各种新的方式来让数据参与决策。现在企业的各个部门都是数据驱动的,从服务已有客户,到获取新客户、增加客户留存率、优化产品、预测销量,以及分析各种趋势。这种对数据的依赖也意味着软件架构实际上是为数据服务的,保证正确无误的数据可以被企业的各个部门获取并加以利用。

本书的作者在几十年前分布式系统刚开始流行的时候就搭建过很多此类系统。然而我们想知道为什么在现代微服务架构中,做决策似乎变得更难了。最终我们意识到,在早期的分布式架构中,数据基本还是放在一个大型关系型数据库里。然而,在微服务以及领域驱动设计( https://oreil.ly/bW8CH )中限界上下文的理念坚持下,数据变成了一种架构考量,作为限制实现细节耦合的一种方式,和事务性一样。很多现代架构中的难题都源自数据和架构考量之间的拉锯,我们将在第一部分和第二部分中一一厘清。

在随后的很多章节中都涉及一个重要的概念区分:业务型数据和分析型数据。

业务型数据(operational data)

支撑业务运营的数据,包括销售数据、交易数据、库存数据等。这类数据支撑着公司的正常运转,被定义为联机事务处理(OLTP),通常包括对数据库中的数据进行增、改、删。

分析型数据(analytical data)

分析型数据指数据科学家和其他业务分析师用来做预测、趋势分析等商业智能活动的数据。这类数据通常既不是事务性的也不是关系型的——与它原始的事务性形态不同,它可以保存在图数据库中,也可以是另一种格式的快照。这类数据对日常运营来说并不是至关重要的,但对长远的战略方向和决策来说却十分关键。

我们会在整本书中讲解业务数据和分析数据带来的影响。

1.4 架构决策记录

记录架构决策的最有效方式之一是通过架构决策记录(ADR; https://adr.github.io )。ADR源自Michael Nygard的一篇博客( https://oreil.ly/yDcU2 ),后来被Thoughtworks Technology Radar( https://oreil.ly/0nwHw )评定为“采纳”。一份ADR由一个简短的文本文件构成(通常只有一两页长),描述一个具体的架构决策。虽然ADR可以使用纯文本编写,但人们通常会使用文本文档格式,例如AsciiDoc( http://asciidoc.org )或Markdown( https://www.markdownguide.org )。ADR也可以使用wiki页面模板来编写。在 Fundamentals of Software Architecture (O'Reilly)一书中,我们用整整一章来介绍ADR。

我们将使用ADR来记录本书中的各种架构决策。对于每一个架构决策,我们将使用如下的ADR格式:

ADR:包含架构决策的简短名词短语

上下文(context)

在该部分,我们会加一两句话来描述它所要解决的问题,并罗列可选的解决方案。

决策(decision)

我们将在这里详述架构决策,并解释为什么这样做。

后果(consequence)

该部分会列举采用这个架构决策后产生的影响,并讨论为此考虑的权衡。

对架构师来说,记录决策是重要的,但保证决策被正确地执行则更加重要。幸运的是,现代工程实践使得我们可以通过架构适应度函数(architecture fitness function)来自动化很多常见的治理问题。

1.5 架构适应度函数

在架构师确定了组件之间的关系并用代码实现其设计之后,应该如何保证其他的实现者会遵从设计呢?更宽泛地说,如果不亲手实现的话,架构师应该如何保证他们设立的设计原则成为现实呢?

这些问题都可以归类为架构治理,适用于对软件开发的任何有组织的监管。因为本书主要讲的是架构结构,所以我们会在很多地方介绍如何通过适应度函数自动化设计和质量原则。

软件开发随着时间缓慢地演变,以适应特定的工程实践。在早期的软件开发中,经常用制造业来类比软件实践,无论是大型的(例如瀑布开发过程)还是小型的(项目的集成实践)。在20世纪90年代早期,Kent Beck和C3项目 上的其他工程师发起了对软件开发工程实践的重新思考,他们叫它极限编程(XP),详述了增量反馈和自动化对软件开发生产效率的关键作用。在21世纪初,软件的开发和运维碰撞出了同样的思维火花,催生了DevOps这个新角色,同时自动化了很多曾经需要手工运行的工作。就像以前一样,自动化让团队前进得更快了,因为团队成员不用再担心什么东西突然悄无声息地出现问题。因此,自动化和反馈成为高效软件开发的信条。 2

让我们回想致使自动化有了突破性进展的环境和场景。在持续集成(CI)出现之前,绝大部分软件项目都要经历漫长的集成阶段。每个开发人员被认为需要在一定程度上独立于其他人工作,然后在最后的集成阶段汇总所有的代码。这种实践的余音还萦绕在版本控制工具里,强制分支从而阻止持续集成。不出意外,项目规模和集成之痛这两者之间存在强关联。通过开创持续集成,XP团队用事实证明了快速、持续的反馈所具有的价值。

DevOps的变革有一段相似的历程。随着Linux和其他开源软件对企业来说变得“足够好”,加上可以程序化定义(终于有了)虚拟机的工具的降临,运维人员意识到他们可以自动化机器定义和其他很多重复工作。

在这两种场景里,得益于技术的发展和洞察力,昂贵人力进行的重复性工作被自动化所取代,这也正是当前在大多数组织里架构治理的现状。举例来说,如果架构师选择了一种特定的架构风格或者通信媒介,如何确保开发人员可以正确地实施呢?如果手动去保证,架构师需要组织代码审查或者召开架构审查委员会来评估治理状态。然而,就如计算机里的手动配置一样,重要的细节很容易被浮于表面的审查所忽略。

使用适应度函数

在2017年出版的 Building Evolutionary Architectures (O'Reilly)一书中,作者(Neal Ford、Rebecca Parsons和Patrick Kua)定义了架构适应度函数:它是能够对于某个(或一组)架构特征进行客观的完整性评估的任何机制。

任何机制

架构师可以使用多种不同的工具来实现适应度函数,本书中将会演示非常多的例子。例如,有专门用来测试架构结构的测试库,架构师可以使用监控设备测试运维性架构的特征(例如性能或可伸缩性),使用混沌工程框架测试可靠性和弹性。

客观的完整性评估

自动化治理的一个关键点便是架构特征的客观定义。举例来说,架构师不能说我想要一个“高性能”的网站,而必须提供一个可以被测试、监控或者其他适应度函数来测定的目标值。

架构师必须警惕那些复合的架构特征,它们本身不能被客观地度量,却由其他可以度量的东西组成。例如,“敏捷度”是不可度量的,但是如果把宽泛的“敏捷度”拆解开,它的目标是无论是在生态系统还是领域中,团队都能够快速响应、大胆应变。从而架构师可以找到组成敏捷度的可度量特征:可部署性、可测试性、周期等。通常,缺乏度量某个架构特征的能力是因为这个定义本身过于模糊。如果架构师致力于寻求可度量属性,那么他们就可以自动化适应度函数应用程序。

某个(或一组)架构特征

这里的特征描述的是适应度函数的两种类型:

原子的

这类适应度函数在隔离条件下验证单一架构特征。例如,在单个代码库里检查组件周期的适应度函数在这个范围内是原子的。

整体的

整体适应度函数验证一组架构特征。架构特征复杂化的一个征兆是它们有时和其他架构特征协同出现。例如,如果架构师想要增强安全性,则很可能会影响性能。类似地,可伸缩性和弹性可能是互相矛盾的——支持大量并发用户可能会使得突发流量更加难以处理。整体适应度函数运用一组连锁的架构特征来确保其综合效应不会给架构带来负面影响。

架构师利用适应度函数来保护架构特征不被改变。在敏捷软件开发的世界里,程序员编写单元、功能和用户验收测试,以此来验证领域设计的不同维度。然而,直到现在,没有类似的机制来验证设计的架构特征部分。事实上,区分适应度函数和单元测试给架构师提供了很好的界定范围的方式。适应度函数验证架构特征,而不是领域标准;单元测试恰恰相反。因此,架构师可以通过问题“执行这个测试需要任何领域知识吗?”来决定使用哪种测试,如果答案是需要,则使用单元、功能和用户验收测试;如果不需要,则使用适应度函数。

例如,当架构师说到弹性时,指的是应用程序承受用户激增的能力。注意,架构师不需要知道任何领域细节,因此,弹性是一个架构考量,在适应度函数的范畴内。与此相反,如果架构师想要验证一个邮寄地址的各个有效部分,这就是传统测试的范畴了。当然,这种区分不是绝对的,一些适应度函数会触及领域,反之亦然,但是其目标的差异性提供了一个很好的思维方式来区分它们。

为了使概念不那么抽象,下面举几个例子。

架构师常见的目标之一就是在代码库中保持良好的内部结构完整性。然而,黑暗势力在各种平台上和架构师的美好意图作对。例如,在任何流行的Java或 .NET开发环境中编写代码时,一旦程序员使用了任何还没引入的类,IDE(集成开发环境)就会善解人意地冒出一个提示框,询问是否需要自动引入这个引用。这个场景太常见了,以至于大部分程序员养成了顺手确认自动引入以便让提示框快点消失的习惯。

然而,在组件间随意地引用类或组件给模块化带来了灾难性的问题。例如,图1-1展示了一个架构师想极力避免的典型的破坏性反模式。

图1-1:组件之间的循环依赖

在这个反模式中,每个组件都依赖其他组件。这样的组件网络会破坏模块化,因为开发者不能单独复用某一个组件,而是必须带上其他组件。如果其他组件也这样和另外的组件耦合,那么架构最终将杂糅成另一个反模式:大泥球(Big Ball of Mud, https://oreil.ly/usx7p )。除了一直在背后监视那些迫不及待敲回车的程序员以外,架构师该怎么管理这样的行为呢?代码审查可以起到一些作用,但是因为发生的太迟而鲜有成效。如果架构师允许开发团队在代码库里胡乱引用,到一周后的代码审查时,一些严重的破坏早已发生。

这个问题的解决办法就是编写一个适应度函数,来阻止组件循环,如示例1-1所示。

示例1-1:用于检测组件循环的适应度函数

上述代码中,架构师用度量工具JDepend( https://oreil.ly/ozzzk )来检查包与包之间的依赖。这个工具可以理解Java包结构,当存在循环依赖的时候,不让测试通过。架构师可以把这个测试加入项目的持续构建里,这样就不用担心这些手速惊人的程序员不经意引入循环依赖了。这是个很好的例子,证明适应度函数可以捍卫那些重要但不紧急的软件开发实践。对于架构师来说,这是一个重要的问题,而且几乎不会影响日常编码。

示例1-1是一个非常底层、以代码为中心的适应度函数。很多流行的代码整洁工具(例如SonarQube, https://www.sonarqube.org )实现了很多可供一键式使用的适应度函数。然而,与微服务架构一样,架构师可能也想验证架构的宏观结构。当设计如图1-2所示的分层架构时,架构师为了隔离关注点定义了不同的层。

图1-2:传统的分层架构

然而,架构师又该如何保证开发人员会遵从这样的分层呢?一些开发人员可能不理解模式的重要性,为了解决一些棘手的局部问题(比如性能),另外一些人可能会秉持“先斩后奏”的态度。允许实施者破坏架构的根基会伤害其长期健康。

ArchUnit( https://www.archunit.org )允许架构师通过适应度函数来解决这一问题,如示例1-2所示。

示例1-2:ArchUnit适应度函数治理分层

在示例1-2中,架构师定义所需的层之间的关系,然后用一个验证适应度函数来管理它。这使得架构师能够在图表及其他信息性工件以外建立架构原则,并且可以持续验证它们。

在.NET领域有一个类似的工具NetArchTest( https://oreil.ly/EMXpv ),在其平台上可以运行类似的测试。示例1-3展示了一个C#中的分层校验。

示例1-3:用于检查层依赖的NetArchTest

这个领域中新工具持续出现,且愈加先进。随着本书很多解决方案用到适应度函数,我们将会持续关注这项技术。

确定适应度函数的客观结果是至关重要的。客观并不意味着静态。一些适应度函数会有和上下文无关的返回值,例如true/false或数值(如性能阈值)。然而,其他适应度函数(被视为动态的)的返回值是由上下文决定的。例如,当度量可伸缩性(scalability)时,架构师既度量并发用户的总量,也度量单个用户的性能。正常情况下,在架构师设计的系统中,用户数量增加时,每个用户的性能会略微降低(但不是断崖式下跌)。因此,对于这些系统,架构师会考虑并发用户的总量来设计性能适应度函数。只要一个架构特征的度量是客观的,架构师就能测试它。

尽管大多数适应度函数应该是自动化并持续运行的,但还是存在一些必须要手动运行的情况。手动适应度函数需要人来进行验证。举例来说,对于含有敏感法律信息的系统,需要律师去审查关键部分的变化以保证其合法性,这就没办法自动化。大多数部署流水线支持手动阶段,允许团队安排手动适应度函数。理想情况下,它们最好尽可能频繁地运行——不运行的验证什么也验证不了。团队要么按需运行这些适应度函数(较少见),要么把它作为持续集成工作流的一部分(最常见)。想要尽可能从适应度函数这类校验中获益,就应该持续地运行适应度函数。

下面这个使用适应度函数来做企业级治理的例子告诉我们,持续性很重要。考虑这个场景:假如企业正在使用的某个开发框架或者库发现了一个零日漏洞(zero-day exploit),这时该怎么办?在大多数公司里,安全专家会把用到这个有问题的版本的地方翻个底朝天,确保它们都更新了。然而这个过程很少是自动化的,大都依赖于很多人工步骤。这不是一个抽象问题,一家主要的金融机构的Equifax,就因为上述这般流程导致了数据泄露事件。如前面描述的架构治理问题所述,人工环节容易出错还容易忽略细节。

Equifax数据泄露事件

2017年9月7日,Equifax(美国一家信用评分机构)发表公告称其发生了数据泄露。最终,这个问题被追溯到有人利用了Java生态中流行的Struts Web框架的一个漏洞(Apache Struts vCVE-2017-5638)。基金会发表了一项声明告知了这个漏洞并在2017年3月7日发布了补丁。美国国土安全部第二天就联系了Equifax和其他类似公司,警示它们有此问题。它们也在2017年3月15日进行了扫描,但并没有发现所有受到该影响的系统。因此,直到2017年7月29日这天,很多老系统也没有应用这个至关重要的补丁,此时Equifax的安全专家监测到了黑客行为,致使这次数据泄露事件发生。

想象有另外一个平行世界,在那里每个项目都运行着一个部署流水线,安全团队在每个团队的部署流水线上都有个“槽”,可以部署自己的适应度函数。大多数时间,这就是一些平常的安全检查,防止开发人员把密码存在数据库里,以及诸如此类的日常管理琐事。然而,一旦有零日漏洞出现,这个机制可以让安全团队在每个项目里插入一个测试来查验某个特定框架及其版本号。如果发现了有危险的版本,就会使构建失败并通知安全团队。团队配置流水线让其响应系统中的任何变化:代码、数据库模式、部署配置以及适应度函数。如此,企业便可以全面自动化重要的治理任务。

架构师可以从适应度函数获益良多,最重要的是他们有机会重新编写代码!架构师最常抱怨的就是他们几乎没时间写代码——但是适应度函数基本都是代码!架构师必须理解这个系统及其未来的演进才能够搭建可执行的架构规范。任何人可以在任何时间通过运行项目的构建来验证这个规范。同时这也结合了架构师的核心目标,即随着项目增长持续跟进项目代码。

架构师应该避免过度使用强大的适应度函数。他们不应该蜷缩在象牙塔里筹谋一个复杂到不可思议、环环相扣的适应度函数,那样只会让开发人员和团队感到沮丧和挫败。相反,适应度函数可以是架构师为软件项目构建重要而不紧急原则的可执行清单的一种方式。很多项目十万火急,允许忽略一些重要的原则。这通常是技术债的来源:“我们知道这样做不好,回头就改”——然后永不回头。通过把代码质量、架构和其他保护措施写成适应度函数代码,并持续运行,架构师可以构建一个开发人员无法跳过的质量清单。

几年前,Atul Gawande写了一本杰出的书—— The Checklist Manifesto (Picador出版社),书中重点介绍了外科手术医生,飞行员等专业人士和其他领域的人员对清单的使用,清单(checklist)在他们的工作中非常重要(有时是法律要求)。这不是因为他们不了解自己的工作或者特别健忘,当专业人士一遍一遍地重复相同的任务时,很容易无意中跳过一步而不自知,清单正是用来防止此事发生的。适应度函数代表了架构师定义的重要原则清单,作为构建中的一部分用于保证开发人员不会无意或有意地忽略它们。

在本书中,当有机会演示如何让架构解决方案和最初设计保持一致时,我们就会使用适应度函数。

1.6 架构与设计:保持定义简单

一个让架构师永远抓狂的问题便是如何让架构和设计作为独立而又关联的活动。关于两者区别的争论没完没了,我们不想趟这摊浑水。出于以下原因,我们将在本书中力求坚定地站在架构这一边。

首先,为了做出有效的决策,架构师必须理解底层架构原则。例如,选用同步通信还是异步通信会带来一系列利弊,架构师在一头扎进实现细节之前需要仔细考虑。在 Fundamentals of Software Architecture 一书中,作者定义了软件架构的第二定理:为什么做比如何做更重要。虽然架构师最终还是要知道如何实现方案,但他们必须首先知道为什么这个选择优于另外一个。

其次,把重点放在架构的概念上,避免深入这些概念的无数种实现方式。架构师可以用各种方式实现异步通信,但我们关注的是为什么架构师应该选择异步通信,这里对具体实现细节不做探讨。

最后,如果开始深入书中各种选择的实现中去,这将会是我们写过的最长的书。关注架构原则可以让其保持尽可能通用。

在本书中,我们使用如下定义。

服务

通俗地说,服务是可以独立部署和执行的功能的内聚集合。我们讨论的大多数关于服务的概念都应用于分布式架构,尤其是微服务架构。

在第2章定义的术语中,服务是架构量子的一部分,也进一步包含服务和其他量子之间的静态耦合和动态耦合等定义。

耦合

如果一个工件(包括服务)中的变化需要另外一个工件也做出改变才能保持功能正常,则两者是耦合的。

组件

应用程序的架构组成要素,执行某种业务或者基础设施功能,通常可以通过包结构(Java)、命名空间(C#)或源代码文件在目录结构中的物理分组来声明。例如,组件Order History可以通过在命名空间app.business.order.history里的一系列类文件来实现。

同步通信

如果调用者必须得到响应才能进行下一步,那么这两个工件是同步通信的。

异步通信

如果调用者不需要等待响应就可以继续运行,那么这两个工件是异步通信的。或者,当请求完成时,接收者可以通过单独的通道通知调用者。

集中编排

如果工作流包含一个主要职责是编排工作流中的服务,那么这个工作流是集中编排的。

分散协作

如果没有编排者,则这个工作流是分散协作的。更准确地说,工作流中的服务共同分担了协调职责。

原子性

如果工作流中的所有部件一直保持统一的状态,则称这个工作流是原子性的。反过来,它们也可以保持不同程度的最终一致性,这在第6章中会详细阐述。

契约

我们广泛使用契约来定义两个软件部分之间的接口,它可能包括方法调用和函数调用、集成架构远程调用、依赖项等。两个软件有交集的任何地方,契约就会出现。

软件架构天生就是抽象的。我们不可能知道读者面对的是什么问题,各种平台、技术、商业软件和其他令人头晕目眩的可能性组成了独一无二的情况,我们只知道没有完全相似的情况。我们讲到了很多抽象概念,但必须让它们落地,通过一些实现细节把它们具象化。为此,我们需要一个具体的问题来阐述架构概念——Sysops Squad。

1.7 引入Sysops Squad的传奇故事

我们在本书中讨论了很多传奇故事,既有字面意义的也有比喻意义的。架构师选择了Saga这个术语来描述分布式架构(详见第12章)中的事务性行为。然而,关于架构的讨论总是趋于抽象,尤其是考虑抽象问题(比如架构的困难部分)的时候。为了解决这个问题,并给我们所讨论的解决方案提供现实世界的上下文,我们开始创作一部关于Sysops Squad的传奇故事。

我们在每章里都用Sysops Squad的传奇故事来阐明本书中所描述的技术和权衡。很多关于软件架构的书讲的都是新的开发工作,而现实世界的很多问题其实是存在于已有系统中的。因此,我们的故事开始于现有的Sysops Squad架构。

Penultimate Electronics是一家大型电子巨头,在全国有数不清的零售门店。当客户购买计算机、电视、音响等电子设备时,他们可以选择购买保修计划。当发生问题时,面向客户的技术专家(Sysops Squad)就会来到客户的住所(或办公地点)解决设备问题。

Sysops Squad工单应用有以下四类主要用户:

管理员

管理员维护系统内部用户,包括专家名单及其对应的技能、位置和档期。同时也使用系统来管理所有客户的账单处理,以及静态参考数据(比如支持的产品、系统中的键值对等)。

客户

客户可以注册Sysops Squad服务,维护用户信息、保修合同和账单信息。客户在系统里发起问题工单,在服务完成后填写用户调查。

Sysops Squad 专家

专家会被分配到问题工单上,根据工单内容解决问题。他们也使用知识库来搜寻客户问题的解决方案,写一些维修笔记。

经理

经理持续跟踪问题工单,接收Sysops Squad问题工单系统的运营和分析报表。

1.7.1 非工单工作流

非工单工作流包括那些由管理员、经理和客户发起的与问题工单无关的行为。工作流如下:1. 系统中Sysops Squad专家的添加和管理,管理员可以填写他们的位置、档期和技能。

2.客户注册Sysops Squad系统,根据购买的产品不同,可以有多个保修计划。

3.按客户在资料中填写的信用卡信息每月自动扣费。客户可以通过系统查看历史账单及其明细。

4.经理请求和查收各种运营和分析报告,包括财务报告、专家绩效报告和工单报告。

1.7.2 工单工作流

工单工作流从客户输入一个问题工单到系统里时开始,到维修后客户完成用户调查时结束。工作流如下:

1.购买了保修计划的客户可以通过Sysops Squad网站来提交问题工单。

2.工单提交之后,系统会根据技能、当前位置、服务区域和档期来决定最适合这个工单的专家。

3.任务分配之后,问题工单会上传到Sysops Squad专家移动设备上专门定制的移动应用程序。专家也会收到关于新工单的文字信息。

4.客户会收到短信或者email(根据设定偏好)提醒他们专家已经在路上了。

5.专家会在手机上使用这个定制的移动应用程序来获取工单信息和定位。Sysops Squad专家也可以通过移动应用程序使用知识库,查看以往是怎么处理此类问题的。

6.专家解决问题之后,他们会把工单标记为“完成”,还可以给问题补充信息,完善知识库。

7.系统接收到工单已完成的消息后,会给客户发一封带有调查链接的邮件,客户可以随后填写。

8.系统收到客户完成填写调查的信息并记录。

1.7.3 糟糕的局面

Sysops Squad问题工单系统近来不太顺利。目前的工单系统是很多年前开发的一个大型单体应用。客户抱怨因为丢单导致咨询师迟迟不出现,而且经常会出现咨询师到了现场才发现自己对要修理的东西一无所知的情况。客户还抱怨说提交新问题的时候,系统有时不可用。

在这个大块头里改东西也是困难和风险齐飞。每改一点点东西,总需要花费大量时间,还经常会破坏别的东西。由于可靠性问题,系统经常死机或崩溃。在定位问题和重启系统的5分钟到两个小时里,整个系统的所有功能在任何地方都不可用。

如果不快点做些什么,Penultimate Electronics公司就只能放弃利润颇丰的保修合同业务线,解雇所有Sysops Squad的管理员、专家、经理和IT开发人员——包括架构师。

1.7.4 Sysops Squad架构组件

Sysops Squad应用的单体系统处理工单管理、运营报告、客户注册和账单,还有一般的管理功能,比如用户维护、登录,以及专家技能和档案维护。图1-3及其对应的表1-1解释和说明了现有单体应用的组件(命名空间中的ss.特指Sysops Squad应用上下文)。

图1-3:现有Sysops Squad应用的组件

表1-1:现有Sysops Squad应用的组件

表1-1:现有Sysops Squad应用的组件(续)

在随后的章节里,我们会把该应用逐渐拆解成分布式架构,在这个过程中会用这些组件来说明各种技术和权衡。

1.7.5 Sysops Squad数据模型

表1-1中Sysops Squad应用程序的各种组件仅使用了数据库中的单一模式来托管所有的表和相关数据库代码。数据库用来持久化客户、用户、合同、账单、支付、知识库和客户调查。表1-2罗列了所有的数据库表,图1-4展示了ER模型。

图1-4:现有Sysops Squad应用程序的数据模型

表1-2:现有Sysops Squad数据库表

表1-2:现有Sysops Squad数据库表(续)

Sysops数据模型是标准的第三范式数据模型,只有几个存储过程和触发器。然而,存在一定数量的视图,主要是报表(Reporting)组件在用。当架构团队人员尝试拆解应用程序为分布式架构时,他们需要和数据库团队一起在数据库层面完成任务。这个数据库表和视图的设定将会贯穿在整本书里,用以探讨为了拆分数据库而采用的不同的技术和权衡。 RTBrpG9kGTxCNQz2Hkc9GA6kM9wdf7RqfdyMWZ793TrkFbTavHeSN5z/k+wHKnZB

点击中间区域
呼出菜单
上一章
目录
下一章
×