如果您是一位面向对象的程序员,您将理解图 1 中的代码片段,即使您不熟悉该语言(C#,但这无关紧要)。您不会惊讶地得知此程序将在控制台上打印以下行:woof
现在您可能对让狗说“woof”不感兴趣,但让这只狗叫的原理与运行世界上最复杂的银行系统的原理相同。
那么为什么要用狗呢?狗有两个可爱的品质,使它们比银行系统更值得研究。首先,它们比银行家可爱得多。其次,它们的实现很简单,因此不会分散本文重点的注意力。回到我的 Dog。
只需对我们的代码进行一些调整,并对编译器开关进行一两次切换,您就可以将这个 Dog 类变成一个分布式的 Dog 组件。再进行一些调整和切换,就可以将同一个 Dog 类变成一个支持 Internet 的 Dog Web 服务。编译器供应商使将对象转换为组件或 Web 服务变得容易。
不错,是吧?
不幸的是,存在一个问题:编译器供应商做得太好了。将对象转换为组件或 Web 服务的能力非常强大。可能过于强大了。
莎士比亚说过:“我拥有力量,但对于我的力量和性质,我尚未受到指示”(《一报还一报》,第一幕,第一场)。简而言之,这概括了对象、组件和 Web 服务的困境。编译器供应商给了我们转型的力量,但没有指示何时以及如何使用这种力量。结果是,应该成为对象、应该成为组件以及应该成为 Web 服务的界限非常模糊。
在研究为什么应该或不应该随意将对象转换为组件和 Web 服务之前,我们需要更仔细地研究一下成为对象、组件或 Web 服务究竟意味着什么。
对象、组件和 Web 服务有许多共同的特征。具体来说
• 所有都是可以做一些事情的代码块。
• 所有都具有描述它们可以做什么的接口。
• 所有都存在于某个进程中。
• 所有都为了响应客户端的请求而存在。
• 所有都支持客户端通过“调用方法”发出请求的概念。
• 所有都可以用图 2 来描述。
这三个实体之间的差异主要由两个因素驱动:位置和环境。位置指的是实体(例如,对象)和客户端的相对位置,或者更具体地说,是实体和客户端所在的进程的相对位置。环境指的是实体和客户端的宿主运行时环境(例如,IBM 的 WebSphere 或 Microsoft 的 .NET)。
对象、组件和 Web 服务还有许多其他差异(有些相当出乎意料),但它们主要是位置和环境的衍生物。因此,让我们从这些开始。
当实体和客户端都位于同一进程中时,该关系被描述为对象关系。实体和客户端的环境必须相同,因为单个进程不能存在于多个环境中。例如,我们可以在 Java 或 C# 中实现对象关系,这两种都是成熟的面向对象技术。
当实体和客户端位于不同的进程中时,环境就成为决定性的特征。当客户端和实体的环境相同时,该关系被描述为组件关系。例如,您可以在 WebSphere 的 EJB(企业 Java Bean)环境或 Microsoft 的 .NET 托管组件环境中实现组件关系,这两种都是流行的组件支持技术。
当客户端和实体的环境不同时,该关系被描述为 Web 服务关系。Web 服务技术包括 SOAP(简单对象访问协议)、WSDL(Web 服务描述语言)、UDDI(通用描述、发现和集成)等。图 3 根据位置和环境的差异对比了对象、组件和 Web 服务。
鉴于此讨论,您可能会想知道为什么我们仍然需要对象或组件。对象和组件关系都可以使用 Web 服务技术来实现。
我们仍然需要对象和组件的原因是效率。我们应该在必须工作的约束条件下尽可能高效地做事。当我们不受需要多个环境的约束时,组件比 Web 服务更有效率。当我们不受需要多个进程的约束时,对象比组件或 Web 服务都更有效率。Web 服务是所有这些系统中效率最低的,除非我们在禁止使用对象或组件的约束下工作。
让我们考虑一下为什么对象比组件更有效率,而组件比 Web 服务更有效率。
由于对象与其客户端位于同一进程中,因此它们的方法解析(将方法调用映射到实际代码)不会跨越进程边界。因此,可以使用某种类型的优化表查找系统来完成方法解析,如图 4 所示。
组件与其客户端位于不同的进程中。它们通常使用透明的(即,客户端不可见的)进程间传输机制,该机制分层在本地对象方法解析之上。通常,这种机制包括客户端进程中的一个代理,该代理接受方法请求,将其打包到进程间通信包中,并将该包发送到组件进程中的一个代理。该代理接收包,确定已请求的方法,然后通过本地对象方法解析发出该请求。
组件比对象慢,因为进程间通信比进程内通信慢。但是,组件确实有一个优化机会:利用本地(例如,.NET)通信协议将请求从客户端移动到组件进程。如图 5 所示。
Web 服务不仅与其客户端在进程边界上分离,而且在环境边界上也分离。这意味着它们必须使用标准化的 Web 服务传输协议(例如,基于 HTTP 的 SOAP),而不是优化的本地进程间传输协议。Web 服务传输协议代表了所有环境供应商之间的协议,并且实际上是最不通用的协议。这些协议以牺牲性能为代价,实现了环境互操作性的通常重要的目标。Web 服务方法解析如图 6 所示。
总而言之,对象方法解析是最快的,因为所有通信都发生在单个进程内。组件方法解析速度较慢,因为它需要进程间通信,尽管是优化的进程间通信。Web 服务方法解析速度最慢,因为它不仅需要进程间通信,而且还使用最不通用的协议来实现这些通信。
显然,您希望使用满足您约束的最快通信方式。让我们考虑构建一个销售点 (POS) 系统。您的 POS 系统可能希望与库存系统交互,例如,告知它有人刚刚购买了一台意式咖啡机,现在是减少意式咖啡机库存的好时机。
世界上有许多库存系统。在构建 POS 系统时,您可能不知道最终将与哪些库存系统交互。同样,库存系统在构建时,可能也不知道哪个 POS 系统会告知它意式咖啡机的销售情况。
由于 POS 和库存系统是独立开发的,因此它们很可能是在不同的环境下编写的。例如,POS 可能是 WebSphere 系统,而库存系统可能是 .NET 系统。尽管 POS 和库存系统有可能是在相同的环境下开发的(例如,两者都是 WebSphere),但您不想冒险。您应该使用环境无关的 Web 服务协议来连接这两个系统。
现在让我们更仔细地看看 POS 本身,而不是它如何与其他系统连接。POS 是由一组协调一致的开发人员构建的。他们不太可能在 WebSphere 中构建 POS 的一部分,而在 .NET 中构建另一部分。他们将选择一个环境并坚持下去。
然而,这并不意味着他们没有使用分布式编程。他们可能确实有 POS 的不同部分在不同的机器和进程上运行,可能是因为他们正在使用三层架构。每个销售站可能都在运行自己的机器/进程;POS 可能还有另一部分在机器上运行,该机器整合整个商店的销售额,另一部分整合整个地区的销售额,还有一部分将数据存储在中央数据存储库中。所有这些都需要分布式;它们
只是不需要不同的环境。因此,最有效的可用技术是组件技术。
在 POS 的给定进程中,可能有数十万行代码来回跳动。对象是组织此代码的好方法。一切都发生在单个进程内,因此您不需要组件的开销,当然也不需要 Web 服务的开销。
对于 POS 架构师来说,这不是在对象、组件和 Web 服务之间进行选择。而是选择将哪种技术用于哪种类型的通信。
因此,好的系统架构不会将对象、组件和 Web 服务视为互斥的选择。相反,它们将它们视为构建块——都很有用,但用途不同。Web 服务用于将自主系统连接在一起;组件用于协调系统内的进程分发;对象用于组织进程内的代码。
典型的系统具有相对较少的跨系统连接点,例如连接 POS 和库存系统的连接点。系统作为一个整体可能存在更多的分发点,并且在任何一个进程中都可能存在大量代码。
因此,您会期望看到一个使用层次结构,它看起来像图 7 所示的金字塔——大量的对象,相对较少的组件,几乎没有任何 Web 服务。这并不是对 Web 服务的评判,而只是对自主系统中通常发现的连接点数量的观察。
您还可以将图 7 的金字塔解释为代码层。在最高层,自主系统(例如,POS 系统)被打包为 Web 服务。在 Web 服务内部,组件用于实现分发。在我们的组件内部,对象组织代码。
表 1 根据到目前为止的讨论比较了对象、组件和 Web 服务。
鉴于图 7 中显示的层次结构和表 1 中总结的各种属性,您可以开始更好地了解对象、组件和 Web 服务如何在实际系统中组合。图 8 显示了销售点和库存系统,以及这三个实体彼此之间可能如何相关的一种可能性。
图 8 显示了对象、组件和 Web 服务的不同功能,这些功能利用了它们的特定能力。Web 服务接收来自外部组织的工作请求。组件执行一些主要的业务功能。对象组织代码。
您可以根据组件提供的功能进一步对 Web 服务内部的组件进行分类。请注意,在图 8 中,不同的组件戴着不同颜色的帽子。我使用帽子颜色来指示组件提供的三种主要功能类型之一
• 入口点组件。这些红帽子组件是 Web 服务内部第一个接收请求的组件。
• 应用程序组件。这些银帽子组件实际上执行 Web 服务的业务功能,例如处理 POS 系统中的销售。
• 出口点组件。这些绿帽子组件负责为其他 Web 服务准备工作请求。
关于对象、三种类型的组件和 Web 服务的这种分类的一个优点是,它可以被编纂成一套更严格的架构原则。我使用的原则是编纂成我称之为软件堡垒模型 (SFM) 的原则。(更多信息:Sessions, R. 2003. Software Fortresses: Modeling Enterprise Architectures. Addison Wesley。)
在 SFM 中,红帽子(入口点)组件称为守卫。银帽子(应用程序)组件称为工人。绿帽子(出口点)组件称为使者。使用 SFM,我会将图 8 重绘为图 9,这突出了 Web 服务作为软件堡垒的自包含性质以及各种组件的专业性质。图 9 通过省略被视为组件实现细节的对象,并忽略被假定等同于组件边界的进程边界而进行了简化。
诸如 SFM 之类的模型的优势在于,它定义了可用作检查系统“正确性”基础的原则。对于软件系统而言,正确性有很多方面,包括安全性、错误处理、性能、可伸缩性和系统的可靠性,除了显而易见的“它是否工作?”之外。
大多数人错误地认为,大型软件系统的正确性主要与构成该系统的对象、组件和 Web 服务的实现细节有关。事实上,正确性更多地与理解这三种类型的实体之间的差异有关。
让我们以安全性为例来说明正确性,重点关注信任这一特定的安全问题。换句话说,我是否信任发出此请求的客户端?在一个大型软件系统中,我应该在哪里问“我信任我的客户端吗?”这个问题?我是在对象、组件还是 Web 服务边界处问这个问题?
这个问题最好通过返回表 1 并查看标记为“构建者关系”的行来回答。在这里,关系指的是实体构建者和客户端构建者之间的关系。
对象列和关系行的交集表示“可能由构建其(对象)客户端的同一个人构建”。你信任自己吗?当然!因此,这不是处理信任问题的正确边界。
组件列和关系行的交集表示“可能由构建其客户端的同一组人构建”。您信任您组中的其他人吗?希望答案是肯定的。
Web 服务列和关系行的交集表示“可能由与构建其客户端的公司不同的公司构建”。您信任另一家公司的人吗?可能不信任。您无法控制他们。您甚至可能不相信他们是他们声称所在的公司。因此,您应该将信任问题集中在 Web 服务边界上。一旦我们跨越了将 Web 服务与其客户端分开的代码边界,信任就不再是一个相关问题,不是因为您不再关心它,而是因为您已经在适当的边界处处理了它。
我在这里没有空间对其他正确性问题进行类似的处理,但信任分析应该让您感受到理解定义对象、组件和 Web 服务的边界的差异的重要性,以及如何提出正确的问题。如有疑问,讨论的起点是表 1。反过来,表 1 是由理解环境和位置是如何“消除”边界模糊性的关键概念驱动的。
语言和工具供应商为我们提供了很好的服务,使我们能够轻松地从对象创建组件和/或 Web 服务。不幸的是,他们给我们留下了一些非常模糊的边界,分隔了这三种非常不同的实体类型。
正确使用对象、组件和 Web 服务的起点是理解这三种实体之间的基本差异,以及这些差异如何定义正确的边界。罗伯特·弗罗斯特在《修墙》中引用了他那脾气暴躁的邻居的话说:“好篱笆造就好邻居。” 当涉及到设计企业软件系统时,他的邻居说得有道理。
喜欢它,讨厌它? 让我们知道
[email protected] 或 www.acmqueue.com/forums
ROGER SESSIONS 是 ObjectWatch 的创始人兼首席执行官,也是六本书的作者,包括《软件堡垒:企业架构建模》(Addison Wesley,2003 年)和数十篇文章。他是国际软件架构师协会的董事会成员。他的每月《架构技术咨询》帮助企业架构师及时了解新的技术趋势以及这些趋势将如何影响他们的软件系统。他的季度《ObjectWatch 时事通讯》定期探究和审查软件行业。这两份出版物均可在 www.objectwatch.com 上找到。他的写作为他赢得了微软的“最有价值专家”称号。
© 2004 1542-7730/04/1200 $5.00
最初发表于 Queue vol. 2, no. 9—
在 数字图书馆 中评论本文
Matt Godbolt - C++ 编译器中的优化
在为编译器提供更多信息时,需要权衡:这会使编译速度变慢。链接时优化等技术可以为您提供两全其美的效果。编译器中的优化不断改进,即将到来的间接调用和虚函数分派方面的改进可能很快就会带来更快的多态性。
Ulan Degenbaev, Michael Lippautz, Hannes Payer - 作为合资企业的垃圾回收
跨组件追踪是一种解决跨组件边界的引用循环问题的方法。只要组件可以形成任意对象图,并且在 API 边界上具有重要的所有权,就会出现此问题。CCT 的增量版本在 V8 和 Blink 中实现,从而能够以安全的方式有效且高效地回收内存。
David Chisnall - C 语言并非低级语言
在最近的 Meltdown 和 Spectre 漏洞之后,值得花一些时间来研究根本原因。这两种漏洞都涉及处理器推测性地执行超出某种访问检查的指令,并允许攻击者通过侧信道观察结果。导致这些漏洞以及其他几个漏洞的功能被添加进来,是为了让 C 程序员继续相信他们正在使用低级语言编程,而这种情况已经几十年不存在了。
Tobias Lauinger, Abdelberi Chaabane, Christo Wilson - 你不应该依赖我
大多数网站都使用 JavaScript 库,其中许多库已知存在漏洞。了解问题的范围以及包含库的许多意外方式只是改进情况的第一步。这里的目标是,本文中包含的信息将有助于为社区提供更好的工具、开发实践和教育工作。