2.软件架构预述(译)
原文: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)
他是由行会讨论和决定的架构的发起人和守护者。他是部门/团队中经验最丰富的开发者之一,恰好承担着分析高层次问题和解决方案的额外职责。在做出架构决策时,他还拥有“质量票”。
可是,有一点值得注意,所有开发者某种程度上都是架构师,因为他们都要了解架构,他们都会议某种形式参与架构,他们都适当地承担着维护架构的职责。
象牙塔架构师(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