文章目录
- 第15章 软件接口
- 15.1接口的概念
- 多个接口
- 操作、事件和属性
- 接口演进
- 15.2 设计接口
- 接口的范围
- 交互方式
- 交换数据的表示形式和结构
- 可扩展标记语言(XML)
- JavaScript 对象表示法(JSON)
- Protocol Buffers
- 错误处理
- 15.3 接口文档化
- 15.4 小结
- 15.5 扩展阅读
- 15.6 问题讨论
第15章 软件接口
与 Cesare Pautasso 合作
美国国家航空航天局(NASA)损失了其价值 1.25 亿美元的火星气候轨道器,原因是航天器工程师在该航天器发射前交换重要数据时未能从英制单位转换为公制单位……
美国国家航空航天局(NASA)的一个导航团队在计算中使用了以毫米和米为单位的公制,而设计和建造航天器的[公司]提供的关键加速度数据则是以英寸、英尺和磅为单位的英制……
从某种意义上说,这艘航天器在单位换算中迷失了。
—Robert Lee Hotz, “Mars Probe Lost Due to Simple Math Error,” Los Angeles Times, October 1, 1999
这一章描述了有关接口的概念,并讨论了如何设计和记录它们。
接口(无论是软件接口还是其他类型的接口)是一个边界,元素在此相遇、交互、通信和协调。元素具有控制对其内部访问的接口。元素也可以细分,每个子元素都有自己的接口。
一个元素的参与者是与其相互作用的其他元素、用户或系统。一个元素与之相互作用的参与者的集合被称为该元素的环境。所谓“相互作用”,我们指的是一个元素所做的任何可能影响另一个元素处理的事情。这种相互作用是该元素接口的一部分。相互作用可以有多种形式,不过大多数涉及控制和/或数据的传输。有些是由标准编程语言结构支持的,例如本地或远程过程调用(RPC)、数据流、共享内存和消息传递。
这些提供了与元素直接交互点的结构被称为资源。其他交互是间接的。例如,在元素 A 上使用资源 X 会使元素 B 处于特定状态这一事实,如果这会影响其他元素的处理,那么使用该资源的其他元素可能需要知道,即使它们从未与元素 A 直接交互。关于 A 的这一事实是 A 与其环境中其他元素之间接口的一部分。在本章中,我们仅关注直接交互。
回想一下,在第 1 章中,我们从元素及其关系的角度定义了架构。在本章中,我们关注一种关系类型。接口是将元素连接在一起所必需的基本抽象机制。它们对系统的可修改性、可用性、可测试性、性能、可集成性等方面有着巨大的影响。此外,作为分布式系统中常见部分的异步接口,需要事件处理程序——这是一种架构元素。
对于给定元素的接口,可以有一个或多个实现,每个实现可能具有不同的性能、可扩展性或可用性保证。同样,对于同一接口的不同实现可能针对不同的平台构建。
到目前为止的讨论暗含了以下三点:
- 所有元素都有接口。 所有元素都与某些参与者进行交互;否则,该元素存在的意义何在?
- 接口是双向的。 在考虑接口时,大多数软件工程师首先想到的是元素所提供内容的概述。该元素提供了哪些方法?它处理哪些事件?但是,一个元素还通过利用其外部资源或以其环境以某种方式表现为前提与其环境进行交互。如果这些资源缺失或环境未如预期表现,该元素就无法正确运行。因此,接口不仅仅是元素提供的内容;接口还包括元素需要的内容。
- 一个元素可以通过同一个接口与多个参与者进行交互。 例如,网络服务器通常会限制可以同时打开的 HTTP 连接数量。
15.1接口的概念
在本节中,我们将讨论多重接口、资源、操作、属性和事件的概念,以及接口的演变。
多个接口
将单个接口拆分为多个接口是可能的。每个接口都有相关的逻辑目的,并为不同类别的参与者服务。多个接口提供了一种关注点分离。特定类别的参与者可能只需要可用功能的子集;此功能可以由其中一个接口提供。相反,元素的提供者可能希望授予参与者不同的访问权限,例如读或写,或者实施安全策略。多个接口支持不同级别的访问。例如,一个元素可能通过其主接口公开其功能,并通过单独的接口提供对调试或性能监控数据或管理功能的访问。可能存在针对匿名参与者的公共只读接口和允许经过身份验证和授权的参与者修改元素状态的私有接口。
资源
资源具有语法和语义:
- 资源语法。语法是资源的签名,其中包括其他程序在编写语法正确的使用该资源的程序时所需的任何信息。签名包括资源的名称、参数(如果有)的名称和数据类型等等。
- 资源语义。调用此资源的结果是什么?语义有多种形式,包括以下内容:
- 为调用该资源的参与者能够访问的数据进行值的赋值。这种值的赋值可能简单到设置一个返回参数的值,也可能影响深远,比如更新一个中央数据库。
- 关于跨越接口的值的假设。
- 使用资源所导致的元素状态的变化。这包括异常情况,例如部分完成操作的副作用。
- 使用资源所导致的将被发出信号的事件或将要发送的消息。
- 由于使用此资源,未来其他资源的行为将如何不同。例如,如果您要求资源销毁一个对象,未来通过其他资源尝试访问该对象可能会因此产生错误。
- 人类可观察到的结果。这些在嵌入式系统中很常见。例如,调用一个打开驾驶舱显示屏的程序会产生非常明显的效果——显示屏亮起。此外,语义的陈述应明确资源的执行是原子性的还是可能被暂停或中断。
操作、事件和属性
所提供接口的资源由操作、事件和属性组成。这些资源还通过对访问每个接口资源时所引起的行为或所交换的数据在语法、结构和语义方面的明确描述来加以补充。(如果没有这种描述,程序员或参与者怎么知道是否或如何使用这些资源呢?)
操作 被调用以将控制和数据传输到元素进行处理。大多数操作也会返回一个结果。操作可能会失败,作为接口的一部分,应该明确参与者如何检测错误,无论是作为输出的一部分发出信号还是通过一些专用的异常处理通道。
此外,事件——通常是异步的——可能在接口中进行描述。传入的事件可以表示从队列中接收的消息,或者要处理的流元素的到达。主动元素——那些不是被动等待被其他元素调用的元素——会产生传出事件,用于向监听者(或订阅者)通知元素内部发生的有趣事情。
除了通过操作和事件传输的数据之外,接口的一个重要方面是元数据,例如访问权限、度量单位或格式假设。这种接口元数据的另一个名称是属性。正如本章开头的引述所强调的,属性值可以影响操作的行为。属性值也会根据元素的状态影响元素的行为。
具有状态且活跃的元素的复杂接口将具有操作、事件和属性的组合特征。
接口演进
所有软件都会演进,包括接口。只要接口本身不变,被接口封装的软件可以自由演进,而不会对使用该接口的元素产生影响。然而,接口是元素与其参与者之间的契约。正如法律合同只能在某些限制内更改一样,软件接口的更改应当谨慎。有三种技术可用于更改接口:弃用、版本控制和扩展。
- 弃用。弃用意味着移除一个接口。弃用接口的最佳实践是向元素的参与者发出广泛通知。从理论上讲,此警告会给参与者留出时间来适应接口的移除。但实际上,许多参与者不会提前进行调整,而是在接口被移除时才发现已弃用。弃用接口的一种技术是引入一个错误代码,表示此接口将在(特定日期)被弃用或者此接口已被弃用。
- 版本控制。多个接口通过保留旧接口并添加新接口来支持演进。当不再需要旧接口或已决定不再支持它时,可以弃用旧接口。这就要求参与者指定其正在使用的接口版本。
- 扩展。扩展接口意味着保持原始接口不变,并向接口添加体现所需更改的新资源。图 15.1(a)展示了原始接口。如果扩展与原始接口不存在任何不兼容,那么元素可以直接实现外部接口,如图15.1(b)所示。相反,如果扩展引入了一些不兼容,那么元素就需要有一个内部接口,并添加一个中介来在外部接口和内部接口之间进行转换,如图15.1©所示。作为不兼容的一个示例,假设原始接口假定公寓号码包含在地址中,但扩展接口将公寓号码作为一个单独的参数分离出来。内部接口会将公寓号码作为一个单独的参数。然后,如果从中介从原始接口被调用,它会解析地址以确定任何公寓号码,而中介会将包含在单独参数中的公寓号码原封不动地传递给内部接口。
图 15.1 (a) 原始接口。(b) 扩展接口。(c) 使用中介。
15.2 设计接口
关于哪些资源应该对外可见的决策应该由使用这些资源的参与者的需求驱动。向接口添加资源意味着承诺只要元素仍在使用,就将这些资源作为接口的一部分进行维护。一旦参与者开始依赖您提供的资源,如果资源被更改或删除,他们的元素将会出现故障。当元素之间的接口契约被打破时,您的架构的可靠性就会受到影响。
这里重点介绍了一些接口的额外设计原则:
- 最小意外原则。接口的行为应当与参与者的预期保持一致。名称在这里起作用:一个恰当命名的资源能给参与者一个关于该资源用途的良好提示。
- 小接口原则。如果两个元素需要交互,让它们交换尽可能少的信息。
- 统一访问原则。避免通过接口泄露实现细节。对于参与者来说,无论资源如何实现,都应以相同的方式访问资源。例如,参与者不应知道一个值是从缓存中返回、通过计算得出,还是从某些外部源新获取的。
- 不要重复自己原则。接口应当提供一组可组合的原语,而不是许多实现相同目标的冗余方式。
一致性是设计清晰接口的一个重要方面。作为架构师,您应该建立并遵循关于资源如何命名、API 参数如何排序以及如何处理错误的约定。当然,并非所有接口都在架构师的控制之下,但在可能的范围内,同一架构的所有元素的接口设计应当保持一致。如果接口遵循底层平台的约定或开发者所期望的编程语言习惯,他们也会很欣赏。然而,一致性不仅仅是赢得开发者的好感,还将有助于最大程度地减少因误解而导致的开发错误数量。
与接口的成功交互需要在以下方面达成一致:
- 接口范围
- 交互方式
- 交换数据的表示形式和结构
- 错误处理
这些中的每一项都构成了设计接口的一个重要方面。我们将依次进行介绍。
接口的范围
接口的范围定义了直接可供参与者使用的资源集合。作为接口设计者,你可能希望公开所有资源;或者,你可能希望限制对某些资源或某些参与者的访问。例如,出于安全、性能管理和可扩展性等原因,你可能希望限制访问。
限制和调解对一个元素或一组元素的资源的访问的一种常见模式是建立一个网关元素。网关(通常称为消息网关)将参与者的请求转换为对目标元素(或多个元素)的资源的请求,因此成为目标元素的参与者。图 15.2提供了一个网关的示例。网关之所以有用,原因如下:
- 一个元素所提供的资源粒度可能与参与者的需求不同。网关可以在元素和参与者之间进行转换。
- 参与者可能需要访问资源的特定子集,或者被限制访问特定子集。
- 资源的具体情况——它们的数量、协议、类型、位置和属性——可能会随时间变化,而网关可以提供一个更稳定的接口。
图 15.2 提供对各种不同资源的访问的网关
现在我们来谈谈设计特定接口的细节。这意味着要决定它应该具有哪些操作、事件和属性。此外,您必须选择合适的数据表示格式和数据语义,以确保您的架构元素相互兼容和互操作。我们开头的引述给出了这些决策重要性的一个例子。
交互方式
接口旨在相互连接,以便不同的元素能够进行通信(传输数据)和协调(传输控制)。这种交互的发生方式有很多种,这取决于通信和协调之间的混合方式,以及元素是共处一地还是远程部署。例如:
- 共处一地的元素的接口可以通过本地共享内存缓冲区提供对大量数据的高效访问。
- 预期同时可用的元素可以使用同步调用以调用它们所需的操作。
- 部署在不可靠的分布式环境中的元素将需要依赖基于消费和产生事件的异步交互,通过消息队列或数据流进行交换。
存在许多不同的交互风格,但我们将重点关注两种最广泛使用的:RPC(远程过程调用)和 REST(表述性状态转移)。
-
远程过程调用(RPC)。RPC 是以命令式语言中的过程调用为模型的,不同之处在于被调用的过程位于网络中的其他位置。程序员编写过程调用时,就好像调用的是本地过程(带有一些语法上的变化);然后该调用被转换为发送到实际调用过程所在的远程元素的消息。最后,结果作为消息发送回调用元素。
RPC 始于 20 世纪 80 年代,自诞生以来经历了多次修改。该协议的早期版本是同步的,消息的参数以文本形式发送。最新的 RPC 版本称为 gRPC,以二进制形式传输参数,是异步的,并支持认证、双向流和流量控制、阻塞或非阻塞绑定以及取消和超时。gRPC 使用 HTTP 2.0 进行传输。
-
表述性状态转移(REST)。REST 是一种网络服务协议。它源自万维网推出时使用的原始协议。REST 包含对元素之间交互施加的一组六项约束:
- 统一接口。所有交互都使用相同的形式(通常是 HTTP)。接口提供方的资源通过 URI(统一资源标识符)进行指定。命名约定应保持一致,并且通常应遵循最小意外原则。
- 客户端 - 服务器。参与者是客户端,资源提供者是使用客户端 - 服务器模式的服务器。
- 无状态。所有客户端 - 服务器交互都是无状态的。也就是说,客户端不应假定服务器保留了关于客户端上一个请求的任何信息。因此,诸如授权之类的交互被编码到令牌中,并且令牌随每个请求传递。
- 可缓存。在适用时对资源应用缓存。缓存可以在服务器端或客户端实现。
- 分层系统架构。“服务器”可以分解为多个独立的元素,这些元素可以独立部署。例如,业务逻辑和数据库可以独立部署。
- 按需代码(可选)。服务器有可能向客户端提供要执行的代码。JavaScript 就是一个例子。
尽管并非是可与 REST 一起使用的唯一协议,但 HTTP 是最常见的选择。由万维网联盟(W3C)标准化的 HTTP 具有<命令>的基本形式。可以包含其他参数,但该协议的核心是命令和 URI。表 15.1列出了 HTTP 中五个最重要的命令,并描述了它们与传统的 CRUD(创建、读取、更新、删除)数据库操作的关系。
表 15.1 HTTP 中最重要的命令及其与 CRUD 数据库操作的关系
“HTTP 命令” | CRUD 操作等效 |
---|---|
post | create |
get | read |
put | update/replace |
patch | update/modify |
delete | delete |
交换数据的表示形式和结构
每个接口都提供了将内部数据表示(通常使用编程语言的数据类型(例如对象、数组、集合)构建)抽象为不同数据表示的机会,即更适合在不同编程语言实现之间交换和通过网络发送的数据表示。从内部表示转换为外部表示被称为“序列化”、“编组”或“转换”。
在接下来的讨论中,我们重点关注为通过网络发送信息选择一种通用的数据交换格式或表示形式。这一决策基于以下考虑因素:
- 表达能力。该表示形式能否序列化任意数据结构?它是否针对对象树进行了优化?它是否需要携带用不同语言编写的文本?
- 互操作性。接口所使用的表示形式是否与参与者的期望相匹配,并且他们是否知道如何解析?标准表示形式(例如本节后面描述的 JSON)将使参与者能够轻松地将通过网络传输的位转换为内部数据结构。该接口是否实现了标准?
- 性能。所选的表示形式是否允许有效利用可用的通信带宽?将表示形式解析为内部元素表示形式的算法复杂度是多少?在发送消息之前准备消息需要花费多少时间?所需带宽的货币成本是多少?
- 隐式耦合。参与者和元素之间共享的哪些假设可能导致在解码消息时出现错误和数据丢失?
- 透明度。是否可以拦截交换的消息并轻松观察其内容?这是一把双刃剑。一方面,如果自描述消息有助于开发人员更轻松地调试消息有效负载,并且使窃听者更容易拦截和解释其内容。另一方面,二进制表示形式,特别是加密的表示形式,需要特殊的调试工具,但更安全。
最常见的独立于编程语言的数据表示风格可以分为文本(例如 XML 或 JSON)和二进制(例如protocol buffers)选项。
可扩展标记语言(XML)
XML 于 1998 年由万维网联盟(W3C)标准化。对文本文档的 XML 注释,称为“标签”,用于通过将信息分解为块或字段并标识每个字段的数据类型来指定如何解释文档中的信息。标签可以用属性进行注释。
XML 是一种元语言:开箱即用,它除了允许您定义一种自定义语言来描述您的数据外,什么都不做。您的自定义语言由一个 XML 模式 定义,其本身就是一个 XML 文档,它指定了您将使用的标签、用于解释每个标签所包含字段的数据类型,以及适用于您文档结构的约束。XML 模式使您作为架构师能够指定丰富的信息结构。
XML 文档出于多种目的被用作结构化数据的表示形式:用于分布式系统中交换的消息(SOAP)、网页的内容(XHTML)、矢量图像(SVG)、商业文档(DOCX)、Web 服务接口描述(WSDL)以及静态配置文件(例如,MacOS 属性列表)。
XML 的一个优势在于,使用这种语言注释的文档可以进行检查,以验证其是否符合模式。这可以防止因文档格式错误而导致的故障,并消除了读取和处理文档的代码进行某些类型错误检查的需要。但权衡之下,解析文档并进行验证在处理和内存方面的成本相对较高。在能够进行验证之前,必须完整读取文档,并且可能需要多次读取才能解组。这种要求,再加上 XML 的冗长,可能会导致不可接受的运行时性能和带宽消耗。虽然在 XML 的鼎盛时期,经常有人提出“XML 具有人类可读性”的论点,但如今这种好处被提及的频率要低得多。
JavaScript 对象表示法(JSON)
JSON 将数据结构化为嵌套的名称/值对和数组数据类型。JSON 符号源于 JavaScript 语言,并于 2013 年首次标准化;然而,如今它独立于任何编程语言。与 XML 一样,JSON 是一种具有自己的模式语言的文本表示形式。然而,与 XML 相比,JSON 要简洁得多,因为字段名称只出现一次。使用名称/值表示而不是开始和结束标签,JSON 文档可以在读取时进行解析。
JSON 数据类型源自 JavaScript 数据类型,与任何现代编程语言的数据类型相似。这使得 JSON 的序列化和反序列化比 XML 高效得多。该符号的最初用例是在浏览器和 Web 服务器之间发送 JavaScript 对象——例如,传输轻量级数据表示以便在浏览器中呈现为 HTML,而不是在服务器端执行呈现并必须下载使用 HTML 表示的更冗长的视图。
Protocol Buffers
协议缓冲区(Protocol Buffer)技术起源于谷歌,在内部使用了几年后,于 2008 年作为开源技术发布。与 JSON 一样,协议缓冲区使用与编程语言数据类型相近的数据类型,使得序列化和反序列化非常高效。与 XML 一样,协议缓冲区消息具有定义有效结构的模式,并且该模式可以指定必需元素、可选元素和嵌套元素。然而,与 XML 和 JSON 都不同的是,协议缓冲区是一种二进制格式,因此它们非常紧凑,能非常有效地利用内存和网络带宽资源。在这方面,协议缓冲区让人回想起更早的一种名为抽象语法标记一(Abstract Syntax Notation One,ASN.1)的二进制表示形式,它起源于 20 世纪 80 年代初,当时网络带宽是一种宝贵的资源,不能浪费任何一位。
协议缓冲区开源项目提供了代码生成器,以便在许多编程语言中轻松使用协议缓冲区。您在一个 proto 文件中指定消息模式,然后由特定语言的协议缓冲区编译器进行编译。编译器生成的程序将被一个参与者用于序列化,并被一个元素用于反序列化数据。
就像使用 XML 和 JSON 时一样,交互的元素可能是用不同的语言编写的。然后每个元素都使用其特定语言的协议缓冲区编译器。尽管协议缓冲区可用于任何数据结构化目的,但它们主要被用作 gRPC 协议的一部分。
协议缓冲区是使用接口描述语言来指定的。由于它们是由特定语言的编译器进行编译的,因此该规范对于确保接口的正确行为是必要的。它还充当接口的文档。将接口规范放在数据库中,可以对其进行搜索,以查看值如何在各个元素中传播。
错误处理
在设计接口时,架构师自然会专注于在一切按计划进行的正常情况下接口应如何使用。然而,现实世界远非正常情况,一个设计良好的系统必须知道如何在面对意外情况时采取适当的行动。当使用无效参数调用操作时会发生什么?当资源所需的内存超过可用内存时会发生什么?当对某个操作的调用永远不会返回,因为它失败了,会发生什么?当接口应该根据传感器的值触发通知事件,但传感器没有响应或响应的是乱码时,会发生什么?
参与者需要知道元素是否正常工作、他们的交互是否成功以及是否发生了错误。为此采取的策略包括以下内容:
- 失败的操作可能会抛出异常。
- 操作可能会返回带有预定义代码的状态指示符,需要对其进行测试以检测错误结果。
- 属性可用于存储数据,以指示最新操作是否成功,或者有状态的元素是否处于错误状态。
- 对于失败的异步交互,可能会触发诸如超时之类的错误事件。
- 可以通过连接到特定的输出数据流来读取错误日志。
用于描述错误结果的是哪些异常、哪些状态代码、哪些事件以及哪些信息的规范,成为了一个元素接口的一部分。常见的错误来源(接口应妥善处理)包括以下内容:
- 不正确、无效或非法的信息被发送到接口——例如,使用不应为空的空值参数调用操作。将错误情况与资源相关联是谨慎的做法。
- 元素处于处理请求的错误状态。元素可能由于之前的操作或同一或另一个参与者之前缺少操作而进入不正确的状态。后者的示例包括在元素初始化完成之前调用操作或读取属性,以及向已被系统操作人员离线的存储设备写入。
- 发生了硬件或软件错误,导致元素无法成功执行。处理器故障、网络无响应以及无法分配更多内存是这类错误情况的示例。
- 元素配置不正确。例如,其数据库连接字符串指向错误的数据库服务器。
指出错误的来源有助于系统选择适当的纠正和恢复策略。具有幂等操作的临时错误可以通过等待并重试来处理。由于无效输入导致的错误需要修复错误请求并重新发送。缺失的依赖项应在重新尝试使用接口之前重新安装。实现中的错误应该通过添加使用失败场景作为额外的测试用例来修复,以避免回归。
15.3 接口文档化
虽然接口包括元素与其环境进行交互的所有方面,但我们选择公开的关于接口的内容——即我们在接口文档中放入的内容——则较为有限。记录每个可能交互的每个方面既不实际,也几乎从来都不是理想的。相反,您应该只公开接口上的参与者与接口进行交互所需知道的内容。换句话说,您选择哪些信息是允许的,以及哪些信息对于人们对元素的假设是合适的。
接口文档指明了其他开发人员在将接口与其他元素结合使用时需要了解的有关接口的信息。随后,开发人员可能会观察到一些属性,这些属性是元素实现方式的一种体现,但接口文档中并未详细说明。由于这些不属于接口文档的一部分,它们可能会发生变化,开发人员使用它们需自担风险。
还要认识到不同的人需要了解关于接口的不同种类的信息。您可能需要在接口文档中包含不同的部分,以适应接口的不同利益相关者。在记录元素的接口时,请牢记以下利益相关者角色:
-
元素的开发人员。需要了解其接口必须履行的契约。开发人员只能测试接口描述中包含的信息。
-
维护人员。一种特殊类型的开发人员,对元素及其接口进行指定的更改,同时尽量减少对现有参与者的干扰。
-
使用该接口的元素的开发人员。需要理解接口的契约以及如何使用它。此类开发人员可以根据接口应支持的用例为接口设计和文档编制过程提供输入。
-
系统集成商和测试人员。将系统从其组成元素组合在一起,并对最终组合的行为有浓厚的兴趣。此角色需要有关元素提供和需要的所有资源和功能的详细信息。
-
分析师。此角色取决于所进行的分析类型。例如,对于性能分析师,接口文档应包括服务水平协议(SLA)保证,以便参与者能够适当地调整其请求。
-
在新系统中寻找可重用资产的架构师。通常从检查前一个系统的元素接口开始。架构师也可能在商业市场中寻找可以购买并完成工作的现成元素。要确定一个元素是否是候选者,架构师对接口资源的功能、其质量属性以及元素提供的任何可变性感兴趣。
描述一个元素的接口意味着对该元素做出其他元素可以依赖的陈述。记录接口意味着您必须描述哪些服务和属性是契约的一部分——这一步骤代表着向参与者承诺该元素确实将履行此契约。任何不违反契约的元素实现都是有效的实现。
必须区分元素的接口和该接口的文档。您对元素所能观察到的部分属于其接口——例如,一项操作所花费的时间。接口的文档涵盖了该行为的一个子集:它规定了我们希望我们的参与者能够依赖的内容。
“海勒姆定律”(www.hyrumslaw.com)指出:“对于一个接口的足够数量的用户来说,您在契约中承诺什么并不重要:您系统的所有可观察行为都会被某些人所依赖。”确实如此。但是,正如我们之前所说,依赖于您未发布的元素接口相关内容的参与者这样做要自担风险。
15.4 小结
架构元素具有接口,这些接口是元素相互交互的边界。接口设计是一项架构职责,因为兼容的接口使具有许多元素的架构能够共同完成有成效和有用的事情。接口的一个主要用途是封装元素的实现,以便此实现可以更改而不影响其他元素。
元素可能有多个接口,为不同类别的参与者提供不同类型的访问和权限。接口说明了元素向其参与者提供哪些资源,以及元素为了正确运行需要从其环境中获取什么。与架构本身一样,接口应该尽可能简单,但不能过于简略。
接口具有操作、事件和属性;这些是架构师可以设计的接口的组成部分。要做到这一点,架构师必须确定元素的
- 接口的范围
- 交互风格
- 交换数据的表示形式和结构
- 错误处理
其中一些问题可以通过标准化手段来解决。例如,数据交换可以使用诸如 XML、JSON 或 Protocol Buffers 之类的机制。
所有软件都会演进,包括接口。可用于更改接口的三种技术是弃用、版本控制和扩展。
接口文档说明了其他开发人员需要了解关于某个接口的哪些内容,以便将其与其他元素结合使用。记录接口涉及决定向元素的参与者公开哪些元素操作、事件和属性,并详细说明接口的语法和语义。
15.5 扩展阅读
要查看邮政地址的 XML 表示形式、JSON 表示形式和 Protocol Buffer 表示形式之间的区别,请访问 https://schema.org/PostalAddress 、https://schema.org/PostalAddress 和 https://github.com/mgravell/protobuf-net/blob/master/src/protogen.site/wwwroot/protoc/google/type/postal_address.proto 。
可以在 https://grpc.io/ 上了解更多关于 gRPC 的信息。
REST 由 Roy Fielding 在他的博士论文中定义:ics.uci.edu/~fielding/pubs/dissertation/top.htm 。
15.6 问题讨论
1. 描述一只狗或您熟悉的其他动物的接口。描述它的操作、事件和属性。狗是否有多个接口(例如,一个针对熟悉的人类,另一个针对陌生人)?
2. 记录灯泡的接口。记录其操作、事件和属性。记录其性能和资源利用率。记录它可能进入的任何错误状态以及结果。您能想到具有您刚刚描述的相同接口的多种实现方式吗?
3. 在什么情况下性能(例如,一项操作所需的时间)应该成为元素已发布接口的一部分?在什么情况下不应该?
4. 假设一个架构元素将用于高可用性系统。这会如何影响其接口文档?假设同一个元素现在将用于高安全性系统。您可能会记录哪些不同的内容?
5. “错误处理”部分列出了许多不同的错误处理策略。对于每一种策略,何时使用是合适的?何时是不合适的?每种策略会增强或削弱哪些质量属性?
6. 如本章开头所述,对于导致火星气候轨道器损失的接口错误,您会采取什么措施来预防?
7. 1996 年 6 月 4 日,一枚阿丽亚娜 5 型火箭在发射仅 37 秒后就极其壮观地失败了。研究这次失败,并讨论更好的接口规范原本可以如何防止它。
8. 数据库模式代表了元素与数据库之间的接口;它提供了访问数据库的元数据。鉴于这种观点,模式演变是接口演变的一种形式。讨论模式可以演变而不破坏现有接口的方式,以及会破坏现有接口的方式。描述弃用、版本控制和扩展如何应用于模式演变。