简介
本文是《righting software》的读书笔记,中文版的名字是《架构之道:软件构建的设计方法》,作者是 Juval Löwy,本文对其中的一些核心观点进行整理。
方法介绍
作者根据自己几十年的设计经验,总结了一套方法:The method = System Design + Project Design。这本书由系统设计和项目设计两部分组成。
在Project Design部分。主要介绍了通过对项目的工作任务进行拆分,绘制出网络图,找到迭代的关键路径,并为不同阶段分配不同的开发资源。在大多数的项目管理培训中都有科普,没有太多的新颖观点,这部分先忽略。本文主要讨论System Design部分。
System Design
避免功能分解
功能分解存在如下问题:
妨碍重用
比如拆分了三个服务 A、B、C,看起来是三个独立的服务,但在执行 B 的时候,其实需要 client 先去访问 A 拿到 B 所需要的参数。访问 C 需要先从 B 中拿一些参数。
这样 A、B、C 都不是独立的服务,他们是一组服务,你没法单独复用任何一个。
数量或规模过度
功能分解的一种途径就是持有尽可能多的服务。因为功能总存在差异,并且通常一个系统可能有数百个功能,这种分解会导致服务“爆炸”。另一种功能分解方法是将执行操作的所有可能方法都合并到大型服务中。这会导致服务的规模膨胀,使它们过于复杂,无法维护。因此,功能分解往往会使服务要么太大、太少,要么太小、太多。我们经常在同一个系统中同时看到这两种痛苦。
客户端臃肿和耦合
功能分解常常导致系统层次结构的扁平化。因为每个服务或构建块都致力于特定的功能,所以必须将这些独立的功能组合到场景所需的行为中。而这经常是由客户端来实现的。,业务流程编排的逻辑使得客户端臃肿了,同时系统的业务逻辑也污染了客户端代码。客户端不再只是调用系统上的操作或向用户呈现信息。
避免领域分解
以建造房屋为例,如果按照领域拆分,那么大概率会划分成下面这样的架构:
作者认为如果我们要在这样的建模下实现睡觉这个功能,那么就需要在每个领域里都实现一遍。(这里我其实觉得和软件领域类比稍微有点牵强)
领域本身无法独立运行,要交付必须完整交付,所以如果一开始就用领域建模,会导致一部分领域反复地被重建。比如你先把厨房做好了,在后面做其它模块的时候,发现排水管有问题,那厨房需要拆了重建。再后面又发现电器线路有问题,又需要拆了重建。这会产生很多资源浪费,且这种浪费是隐性的(这个倒是有道理,如果是比较大的项目,开发阶段某几个模块因为集成问题,反复地返工确实很常见)。
另外单元测试在领域中基本没什么用处,业务需求是不同领域共同作用的结果,即使某个领域的单元测试 100% pass,也不能保证业务逻辑能正常运行。(又一个和常规理念不太一样的观点)
基于易变性分解
基于易变性的分解识别潜在变化的区域,并将这些区域封装到服务或系统构建块中。然后,将所需的行为实现为易变封装区域之间的交互。基于易变性的分解的动机是简单性本身:任何变化都封装在一起,其中包含对系统的影响。
1972 年 David Parnas 发表的 6 页论文 criteria for modularization 已经包含了现代软件工程的大部分元素,包括封装、信息隐藏、内聚、模块和松耦合。这篇文章指出,寻找变化点是分解的关键标准,功能本身不是分解的关键标准。
识别易变性
易变性和可变性
可变性描述了那些可以使用条件逻辑在代码中轻松处理的方面。易变性描述了那些会对整个系统产生连锁反应的变化或风险。变更一定不能让架构失效。并非所有可变因素都是易变的
易变轴
在任何业务中,系统只有两种可能面临变化的方式:第一个轴是同一个客户随着时间的推移发生的变化。即使目前系统完全符合特定客户的需求,但随着时间的推移,该客户的业务环境也会发生变化。第二种情况的变化可能在不同的客户之间同时发生。如果可以冻结时间并检查客户群体,那么现在所有客户使用该系统的方式是否完全相同?他们中的一些人做了哪些与其他人不同的事情?我们必须适应这种差异吗?所有这些变化都定义了易变性的第二个轴。
可以使用易变轴来封装房屋的变化。 观察它是如何随着时间变化的。家具变旧了要换掉,电器老了要换,后续卖给别人的话业主要换,哪天业主不开心了可能想刷个墙外观会变,以此类推。
如房屋结构的易变性,不同房屋的邻居肯定也是不同的,不同的房屋也可能会处在不同的城市。
伪装成需求的解决方案
在接到需求列表的时候,还应该注意有些需求描述的不是需求,而是解决方案,比如烹饪不是需求,吃饭才是需求,烹饪只是个解决方案,在做易变性分析的时候要把这些解决方案进行转换,要找到根源的需求,才能分析出可变性来,比如烹饪会被封装进进食的组件中,该组件负责处理进食的各种可变性
易变列表
在分解系统和创建架构之前,我们应该简单地编制一个易变区域的候选列表,作为需求收集和分析的自然组成部分。应该持开放的态度来对待这份清单。询问在易变轴上可能会发生什么变化。该列表是跟踪观察结果和整理思想的有力工具。不要承诺实际的设计,我们所做的就是维护列表。虽然系统的设计时间不应超过几天,但识别正确的易变区域可能需要相当长的时间
典型分层
采用四层系统架构
客户端:系统的入口点,遵循相同的访问安全性、数据类型和其他接口要求,还封装了客户端中的潜在易变性。
业务逻辑层:还封装了客户端中的潜在易变性,还封装了客户端中的潜在易变性。管理器用于业务流流程编排,封装流程的易变性。引擎用于封装独立的业务逻辑,封装单个业务模块内部的易变性。一个设计良好的管理器服务应该是近似可消耗的,仅仅协调引擎和资源访问。
资源访问层:封装了访问资源时的易变性,支持各种不同基础资源。
资源层:包含系统所依赖的实际物理资源,如数据库、文件系统、缓存或消息队列。
实用工具库栏:包含实用工具库服务。这些服务是几乎所有系统都需要运行的某种形式的公共基础设施,比如安全、日志等。
可组合设计和架构验证
处理变更的诀窍是控制它的影响,避免连锁效应导致变更范围不可控,即局部化影响。
任何给定的系统中,并不是所有的用例都是独特的,大多数用例是其他用例的变体。所需的主要行为有多种排列方式,例如,常规情况、不完整情况、特定区域内特定客户的情况、错误情况,等等。而用例只有两种类型:核心用例和其他用例。核心用例代表了系统业务的本质。
作为架构师,使命就是确定可以组合在一起以满足所有核心用例的最小组件集。因为所有其他用例仅仅是核心用例的变体,常规用例仅仅代表了组件之间不同的交互,而不是不同的分解。这样,当需求改变时,设计无须改变。
任何系统的目标都是满足需求,而可组合设计还可以实现其他功能:设计验证。一旦可以为每个核心用例生成服务之间的交互,就可以生成有效的设计。调用链图是检查用例和演示设计如何支持用例的一种简单而快速的方法。调用链演示满足特定用例所需的组件之间的交互,可以直接将调用链叠加到分层架构图上。图中的组件通过箭头连接,箭头表示组件之间调用的方向和类型——实心黑色箭头表示同步(请求/响应)调用,虚线灰色箭头表示队列调用。
总结
《righting software》提供了一种全新的设计思路,但是相关的实践案例还是比较少的。 很赞同局部化变化影响的思路,但是感觉缺少更加明确的指导原则。 在系统设计阶段,梳理出未来的易变性也不是一件容易的事情,毕竟后续怎么做业务可能业务方都没想好,即使信誓旦旦保证不变的事情也可以随时调整。
参考
架构之道:软件构建的设计方法