原文:https://herbertograca.com/2017/07/05/software-architecture-premises/
这篇文章是软件架构编年史(译)的一部分,这部编年史由一系列关于软件架构的文章组成。在这一系列文章中,我将写下我对软件架构的学习和思考,以及我是如何运用这些知识的。如果你阅读了这个系列中之前的文章,本篇文章的的内容将更有意义。
在这篇文章中,我将总结一些关于软件架构的最基本的概念,了解它们才能更好地理解后续的文章。
没有银弹
无论你如何理解我在软件架构编年史(译)中谈到的内容,首先要理解的是没有银弹,没有“普适性”的解决方案。尽可能地了解不同的方法,理解每一种方法的优劣,和它们解决的特定技术问题。
然后,当接受新的挑战时,先从理解业务和最终用户的需求开始。在搞清楚这些需求之后,你才能思考应该采用哪些架构风格和模式来更好地解决这些问题。
最后,自己做出选择,是实现一种已知的解决方案,还是创造适合自己的特定问题的独特设计。
有些架构风格号称是所有形式的软件的“银弹”。然而,优秀的设计这应该选择最符合解决特定问题需要的风格。——Roy Fielding, 2000 [1]
术语
在软件开发的世界中使用的术语很多都模棱两可,因此,我必须澄清一些我使用的术语的含义,然后继续。
功能性(Functional)
在应用中纯粹发挥技术作用的代码片段、方法、类、类的组合。它们和(业务)领域无关,仅仅代表应用中的一种技术能力。例如:
- 层次(Layer)
- 工厂(Factory)
- 资源库(Repository)
- 值对象(Value Object)
- 视图(View)
- 视图模型(ViewModel)
概念性(Conceptual)
在应用中标识一个(业务)领域概念的代码片段、方法、类、类的组合。它们和领域相关,代表应用中的一种业务能力。例如:
- 用户
- 产品
- 库存管理
- 产品变体
- 结帐
- 销售
这种划分并不是说一个代码单元不能同时具备两种能力(功能性和概念性)。例如,“Money”对象可以表示一个领域概念,同时也被设计成一个值对象。如果我把它当成领域概念,我指的就是领域内的金钱概念,但如果我涉及的是这个类中的功能性方面时,我指的就是值对象的技术特性(没有ID、可以是不变的等等)。
包(Package)
划分在一起的类组成的集合,理想情况下遵循一组规则进行划分。
模块(Module)
我使用Software Architecture in Practice[7]给出的定义,模块就是一个功能性包,它体现了应用中的一种技术能力。它是解耦的并且能够被其他的实现替换。我的理解是,模块即存在与较低的粒度级别,比如,“安全模块”或者“ORM”,也可以存在于像客户端和服务器这样的应用块。模块提供的是功能性内聚。
组件(Component)
我使用Software Architecture in Practice[7]给出的定义,作者将组件定义为一个代表业务能力的概念性包。理想情况下,它也是和其他组件和模块解耦的。例如“用户”、“产品”或“结帐”。
然而,最重要的是要记住,理想情况下,它代表了一个限界上下文(Bounded Context)。组件提供了概念性内聚。
应用(Application)
我将面向用户的代码即 UI 视为应用,它建立在组件之上。例如,我们可以基于一组组件构建网络商店。不管怎样,这个网络商店会提供一个(店面) UI 让用户浏览和购买商品和另一个(管理) UI 让商店管理员管理商品、库存、支付供应商,等等。这是在同样的业务组件之上构建的两个独立的应用。
系统(System)
我认为系统是一组以某种方式在一起工作,为各种企业必需品提供功能,形成一个企业范围内的系统,即企业应用。这些应用可能构建在相同或不同的组件上。在之前网络商店的例子中,系统就是作为一个整体的网络商店,包括两个基于同样业务组件构建的两个应用(店面和管理),还有其他像支付供应商或货运供应商这样的第三方应用。
架构(Architecture)
软件架构的简单定义有很多,我觉得都不错,但我认为理解它是什么很简单,而更重要的是,定义架构的产出,它应该给项目带来什么。
软件架构[…]是系统需要考虑的一组结构,它们包括软件元素和它们之间的关系,以及这些元素和关系的属性。—— Clements et al, 2010 [6]
下面是我考虑架构的方面:
- 横跨所有特性开发的技术决策,例如,框架、代码标准、文档、流程,...;
- 这是存在于项目中的一组很难在后期改变的技术决策 [3];
- 它是系统的全景图[5]:pp.2,粗略的描绘,结构,组件及其关系[4] [6];
- 它使项目做好变化的准备[5]:pp.30,常常是将决策推迟到最后允许的时刻[5]:pp.32;
- 它让项目做好重用组件和模块的准备[7]:pp.29–35;
- 它制定出结果的一致性标准并建立轻量的流程,比如编码规范、开发阶段、持续交付和持续部署;
- 它不是某一个人的职责,而是由来自项目中不同特性团队的开发者组成的行会的职责。
如果你不熟悉行会的概念,可以观看下面关于Spotify 工程师文化的视频:
架构师(Architect)
他是由行会讨论和决定的架构的发起人和守护者。他是部门/团队中经验最丰富的开发者之一,恰好承担着分析高层次问题和解决方案的额外职责。在做出架构决策时,他还拥有“质量票”(He also benefits from a “quality vote” when making an architectural decision.)。
可是,有一点值得注意,所有开发者某种程度上都是架构师,因为他们都要了解架构,他们都会议某种形式参与架构,他们都适当地承担着维护架构的职责。
象牙塔架构师(Ivory Tower Architect)
有一种架构师会做出和架构有关的所有决定,这种万能的象牙塔架构师是一种架构师的反模式。他对其他干系人对架构的贡献既不开放,也不轻易接收,而是阉割了这些贡献。
Smells of a bad Architecture (and bad code) [8]
僵化(Rigidity)
如果软件难以修改是因为修改会导致更多关联修改,软件就是僵化的。它就会变成兔子洞:当我们以为修改快要完成时,突然发现还有更多的代码需要修改,把我们拉进无止尽的轮回之中。
脆弱(Fragility)
脆弱的软件在修改时,总会出现意料之外的、毫无关联的、无法预测的错误。
牢固(Immobility)
如果设计包含一些可以在其它系统中使用的部分,但将这些部分从原系统中分离出来需要大量工作甚至带来许多风险,我们就说设计是牢固的。
粘滞(Viscosity)
在一个粘滞的系统中,要做对困难重重,要做错却轻而易举。这意味着通过正常开发实现变更不如用非常手段来得容易。
如果执行单元测试和/或编译需要耗费很长时间,开发很可能导跳过这些过程,不跑任何自动化测试就实现非常规的修改,这就是系统范围的粘滞。
不必要的重复(Needless repetition)
当时间不够或经验不足导致必要的抽象缺失时,就会产生不必要的重复。这些代码也许并不是直接复制粘贴造成的重复,而是由在不同地方重复定义的相同业务规则带来的。
晦涩(Opacity)
代码写得混乱,难以理解,我们需要深人方法实现的细节才能搞清楚代码要干什么。
不必要的复杂(Needless complexity)
开发者采用了多种不同的抽象和未来潜在变化的应对措施,来积极地避免其他六种坏味道。良好的软件设计是轻量灵活的,理解起来更容易,最重要的是修改更容易,因此不必预判所有未来的潜在变化。
引用来源
[1] 2000 – Roy Fielding – Architectural Styles and the Design of Network-based Software Architectures
[2] 2000 – Robert C. Martin – Design Principles and Design Patterns
[3] 2006 – Booch, in [5 pg.2]
[4] 2007 – IEEE1471 in [5 pg.2]
[5] 2010 – James Coplien, Gertrud Bjornvig – Lean Architecture
[6] 2010 – Paul Clements, Felix Bachmann, Len Bass – Documenting Software Architectures
[7] 2012 – Len Bass, Paul Clements, Rick Kazman – Software Architecture in Practice
[8] 2014 – M. H. Jongerius – THE SEVEN DESIGN SMELLS OF ROTTING SOFTWARE
[9] 2017* – Wikipedia – Software Architecture