6.6 系统确定性是界面原则的核心
通过讨论领域间的组成与关系,我们可以尽量将系统的可变性隔离在较晚实现的域中。因此,这意味着先期建设的系统总是不变的、稳定的、可重用的。这是组成论视角对系统架构的主要贡献。但从系统演进的趋势来说,任何系统的组成部分都必然面临我们持续开发行为将会带来的影响。
而界面(interface)——就提出这一概念的本意来说——就是通过对系统确定性加以规格化,从而来避免上述影响 21 。在我看来,如果一个界面(以及其规格细节)是确切而有效的,那么它应当完全满足如下条件:
- 准确——合适的知识与表达,至少能让交流双方通过某种形式沟通;
- 有用——完全明白的意图,至少与系统架构的意图不违背;
- 可见——执行的效果显而易见,至少在领域或层次上的数据与逻辑流向明确;
- 可能——应当存在实现的手段,至少可以立即着手开始尝试。
在架构的表达上,由于纵向分开的并列部分之间是没有关系的,因此它们之间也就没有规格化的需求。而横向的层次之间,仅有向下依赖是确定的,因此界面必是由下层来规格化的。这应当包括(上层所需的)数据规格与(调用的)逻辑规格两个部分。
究竟是“上层所需”还是“下层具有”决定了规格的细节呢?这是一个相当关键且又颇具争议的问题。从此前的讨论来看,上层总是(在时序上、相对的)还未能确定的,因此依据“上层所需”来决定规格细节显然是缘木求鱼的事情。但反过来说,如果仅凭“下层具有”来确定规格细节,虽然在逻辑上讲得通顺,却又常常因为对这些规格的细节考虑得不够周全而限制了上层的使用,这又是层次架构在实效性上的疑难。
有两种方法来改善这一问题 22 。第一种方法是,我们不必过早追求底层界面细节的准确性与可用性,而仅仅将底层所能提供的功能(逻辑)与数据完整(而又粗略地)表达为接口 Int_L0 v0.1。接下来,在后续的开发活动中,上层的开发活动可以基于这些功能与数据进行设计细化,并将这些设计视为对 Intf_L0 v0.1的封装(例如代理)来实现为 Intf_Ln v1。然后,我们只需要将 Intf_Ln v1下沉到 L0 层,并以之为公共接口 Intf_L0 v1发布即可。简单地说,就是在上层细化接口并交由下层发布。尽管这看起来颇为复杂,但确实是实践中常用的方法。
下面讲第二种方法。参考我们此前的讨论,向下依赖其实只发生于如下两种情况:
- 上层对下层的数据依赖 23 ;
- 当采用嵌套结构时,需要一个向下的注册逻辑。
那么,我们其实是在说:一般层次系统只需要数据界面,而框架/引擎类的层次系统只需要多维护一个注册逻辑即可——所以 REST 和 CRUD 24 事实上成了应付大多数情况的抽象接口方法;即使我们是实现引擎或框架,也只是需要在核心层面考虑清楚注册与回调的机制。
当然,采用第二种方法将意味着上层总是(尽可能多地)在面向数据开发,而非面向既已确定的逻辑,所以这样建立的系统将趋于扁平 25 。这样的架构在应付系统整体规模与复杂性上的能力其实是不够的。因此采用第二种方法常常也是权宜之计,在一段时间之后,仍然会从上层中抽取确定的逻辑来形成新的层次 26 。
但我们如何确定“这是一个界面”或“这些既有的东西可以表达为一个界面”呢?
对于架构中的一条横向的线来说,确定它的界面设计的原则有很多。大致地,则可以分为系统、表现、模块三类原则。其中 “系统原则” 是总的纲要, “模块原则” 是系统自身进行(已经进行和计划进行)层次或领域规划时的指导,而 “表现原则” 是界面的风格与样式方面的约束。表 5 列举并分类 Erlang 的一些实践性原则 27 。
表 5 三类界面原则的示例:Erlang 的一些实践性原则
(续表)
* 写出朴实无华的代码
** 不要在全局声明和使用私有数据结构,不要暴露对象的实现
***“防御”与否是一个有争议的话题
最后,仅就设计的表达来说 28 ,也可以将一些 GoF 模式作为确定的、确实有效的界面设计参考。一般来说,其结构型模式适合数据层次的规划,而行为型模式适合逻辑层次的规划。后者,尤其是在实现嵌套结构的层次化中相当有效。
- 《人月神话》对这一问题也有过充分的讨论,其基本观点是“由于精确性的考虑,我们需要形式化的设计定义,同样,我们需要记叙性定义来加深理解”。 ↩
- 它表明的其实是“普遍/特殊”这样的分类法。 ↩
- 领域与层次的分类依据是架构中非常重要的信息。一般有三种方法来表达它们,其一,通过线条和标注;其二,通过更为明确的分类指称;其三,在其他架构文档中加以详述。 ↩
- 在“第 5 章 系统的架构与决策”中,是把规模作为复杂性的一部分,讨论系统在“领域集总量”上的规模。 ↩
- 在本小节有关复杂性的叙述中,“1”是没有单位的,表明它的确定性;当它与“(计量)单位”同时出现时,才能表明复杂性的大小。本书不讨论“如何计量系统复杂度”的问题(亦即是说不讨论单位的设定),仅以这种抽象描述的细微差异来说明“复杂性的大小与可变”之间是存有区别的。 ↩
- 类似这种“ 性 (状)”的不变,并列与嵌套结构表达的分别是系统的“(总) 量 ”与“(本) 质 ”上的不变。 ↩
- 就规模性而言,本书是将“库”划为应用(application)这个级别的。这里只是对它的构建与表示方法加以讨论,并不是否定此前的规模划分。从另一个角度上看,泛义的“系统”也是可以包括库、程序或功能的,因此其构建与表示方法也有可借鉴之处。 ↩
- 你可以将某些 引擎(Engine) 也视为框架,它是符合这里的讨论的。另外需要补充的是,这里的“框架与平台”与上一节小中的概念是相同的,这两个小节分别讨论到他们的范围(意图、方向与目的)与结构。 ↩
- 例如,引擎层与处理层、驱动层与应用层、框架调度层与业务层等,前者都是核心领域或包含核心过程。 ↩
- 参见《程序原本》第“18.4 聚焦领域之于系统的主要需求:维护状态或接受消息”小节。 ↩
- 可以理解为:向系统追加逻辑,而它们不依赖底层系统时,这些新的逻辑只对系统规模构成影响,而不影响原有系统整体的稳定性——例如我们可以将新的逻辑独立于原系统部署。 ↩
- 进而也会破坏由确定性带来的稳定性,持续性等架构特性。 ↩
- 我们讨论的是“如果确实具有这样的关系”,那么如何在层次中予以处理的问题,而非鼓励这样的关系。 ↩
- 注意我们并没有讨论“同时发生相互依赖”的情况,若 A 和 B 间不存在时序性,则似乎它们并不应当被拆分。以咬合的齿轮为例,若分离二者则齿轮之间的互动性全无;仅当视它们是一体时,才会存在“同时相互依赖”的逻辑。 ↩
- 由于向下的关系是系统中既存的,因此不必讨论从 B 抽离至 A 的情况。 ↩
- 方案的选择取决于成本,例如考虑网络开销或数据重构与存储的开销。 ↩
- 参见《程序原本》一书“第 16 章 依赖”。 ↩
- 绝对的数据与逻辑分离可能导致数据规划、迁移和转送的成本失控。同时,也会导致逻辑间需要维护更多的状态或消息,这一定程度上增大了复杂性(尽管仍然是确定的)。因此,在现实的多个层次之间,通常是既允许向下的数据依赖,也允许向下的逻辑依赖的。 ↩
- 注意,这里是将它们“视作数据”,而非“通过抽取数据层来消除逻辑依赖”。所谓“视作数据”,是抽象概念上的重新定义,请参阅《程序原本》一书“第 3 章 抽象”。 ↩
- 事实上还必须讨论 A..C 与动态数据之间的关系,这取决于“引擎/框架”等具体方案。一般来说,这一关系也是确定的,例如调度关系,又例如设计模式中的策略、命令等。除了 Z(注册逻辑)之外,在对动态数据 D..Z 的使用上,还应当考虑安全性、稳定性等因素,但这些都取决于“框架”的详细设计,并非这里讨论的架构层次规划的问题。 ↩
- 在 Walter F. Tichy 在 ICSE 1992 上的论文“Programming-in-the-Large: Past, Present, and Future”中提及了层次系统与其界面抽象的出处。其译文“大型程序设计的过去、现在和将来”发表于《计算机科学》1992.06 期,译者陈海东。其原文为:“层次模型中体现的数据抽象原理可追溯到 1966 年 Dennis 和 Van Horn 的论文,它强调了用户和内核间的一个简单接口。Dijkstra 在 1968 年报告了第一个可供使用的、内核分为几层的操作系统。” ↩
- 界面的设计是自下而上的,这是层次结构的形成和表达等内在性质所决定的,但其便利性则取决于设计者的经验。所以这里必须强调的是,这些仅仅是依据经验来讨论的一些技巧,并且也仅能予这一局面以有限的改善而已。 ↩
- 逻辑依赖可以通过数据下沉或添加数据抽象层次来变成数据依赖。 ↩
- REST(Representational State Transfer,表述性状态转移)是一种面向远程服务提供的架构方法,它将架构中“端到端”的关系理解为“资源需求”,并将主要接口抽象为 GET、POST、PUT 和 DELETE 四种方法。而 CRUD 则抽象了面向存储/持久层/数据的四种基础操作:Create、Retrieve(Query/Select/Read)、Update 与 Delete。 ↩
- 这里的意思是说层次过少,尤其是具有确定逻辑的层次过少。 ↩
- 平台是从下向上做,还是从上往下做?这个问题的答案其实并不绝对。一般来说,我们能根据经验来确定大体的层次,并在各层的细化中采用“上层实现,下层发布”或“从上层的确定逻辑中抽取层次”的方法。总的来说,这一过程是渐进的,而非一开始就决定的。 ↩
- 引自“Sofrware(SW) Engineering Principles”,出自 Erlang 项目的公开文档“Erlang Programming Rules”。 ↩
- 本书并不详细讨论“架构的设计”或从需求开始的“分析与设计”过程,而是非常严格地区分 架构过程 与 设计过程 。因此架构表达与设计表达并不是相同的意思,前者的目标是从系统中识别出的基本模型,而后者则是在该模型上的细节刻划,因此后者是可以进一步地借助 GoF 与 UML 这样的工具。注意,尽管 UML 图也分结构型与行为型,但这与我们讨论的内容并不“完全对等”。 ↩
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论