我们继续为大家介绍由 Thomas Erl 编著的《SOA 设计模式》,在第一部分, 我们介绍了第 10 章的 3 个目录治理模式。今天,我们介绍第 16 章中的 8 个服务治理模式。其中兼容性变更和版本标识讨论服务版本控制;终止通告关注服务生命 周期中的最后阶段,也就是服务的退休;服务重构阐述了如何处理服务契约的变更;服务分解,代理功能和分解的功能包括了如何使用多个细粒度服务去描述粗粒度 服务的技术;而分布式功能则能够通过服务的延迟辅助实现伸缩性。
注:本章的治理模式关注的是服务架构中与设计相关的治理问题,即将发布的本丛书系列中的《SOA 治理》将带来更广阔的技术和组织结构上的最佳实践及模式。
尽管在分析和建模阶段已经尽了最大努力,使所交付的服务具有广泛的功能,服务依然会面临新情况和新需求的影响,从而挑战最初的设计。好几个模式是基于该原因而萌发的,它们力求在不破坏服务(作为现有服务目录中的一个活动成员的)职能的前提下发展服务。
兼容性变更(Compatible Change, 465)和版本标识(Version Identification,472)关注服务契约的版本控制。类似地,终止服务则重点针对服务及其契约的退休。
本章最基本的模式是服务重构(Service Refactoring,484),它通过一个松散的(理想情况下是解耦的) 契约,帮助实现底层逻辑及实现的升级和改进(而不必影响服务契约)。
服务分解(Service Decomposition,489),分解的功能(Decomposed Capability,504)以及代理功能(Proxy Capability,497)三人组则形成了允许将粗粒度服务分解成多个物理上分离的细粒度服务的技术,从而更进一步地提高复合性能。分布式功能(Distributed Capability,510)也提供了一种特殊的,重构相关的设计时的解决方案,它通过内在的分布处理延迟提高服务的可伸缩性。
兼容性变更
由 David Orchard, Chris Riley 贡献
如何使得对服务契约的修改不影响服务的消费者?
问题
修改已经发布的服务契约可能会影响现有客户端程序,甚至使其失效。
解决
某些对服务契约的变更可以是向后兼容的,因此可以避免对服务消费者的消极影响。
应用
服务企业的变更可以某些方式进行调和,如扩展、放松现有约束,或应用并发契约(Concurrent Contracts 421)等。
影响
兼容性修改仍然会带来版本治理的工作,并且放松约束的技术可能会导致模糊的契约设计。
原则
标准化服务契约,服务松耦合
架构
服务
表 16.1 兼容性变更模式概览
问题
当服务被部署并作为某活动服务目录的一部分之后,其功能就作为一个企业资源而使用了。服务消费者通过调用其契约并与之交互的方式使用其功能。结果,依赖关系在服务契约和服务消费者程序之间就自动形成了。在此之后,如果需要变更契约,就可能会影响到现有消费者,因为它们是根据最初的变更前契约而设计的(图 16.1)。
图 16.1
某服务功能的名字在版本 1 之后被修改,而该版本已经在使用当中,结果,该服务的版本 2 于现有消费者 A 不兼容。
解决
只要有可能,对现有服务器的更改应该要保留契约对现有服务消费者的向后兼容性。这将使得服务契约能够按需发展,同时还能避免依赖于它的组件和消费者程序受到不利影响(图 16.2)。
图 16.2
现有功能没有更名,而是在原功能旁使用新名字命名了一个新能力,这样就可以保证服务 A 和 B 的兼容性。
应用
根据企业的变更需求的不同,有很多相应的应用该模式的技术。该模式的根本目的是避免给服务契约带来不兼容的变更,以至于破坏服务与现有服务消费者之间关系。
这里是一组描述,它们针对常见的服务契约的变更提出了如何使用向后兼容的方式进行这些变更:
- 对 WSDL 文件新增一个操作 —— 可以简单地将新增操作添加到现有定义之后,作为现有契约的扩展,而不会影响任何现有契约内容。
- 重命名现有操作 —— 如前面的图标所描述的,可以通过在现有操作旁边增加新操作的方式重命名该操作,这种方法可以由终止通告(Termination Notification,478)进一步补充,比如,最终要让原操作退休,且同时要允许依赖于该操作的消费者可以从容地转向新命名的操作。
- 删除现有操作 —— 如果某个操作要从 WSDL 文件中永久地删除,该变更不可能以兼容的方式实现。在这种情况下,强烈推荐使用终止通告 (Termination Notification,478),它能给消费者程序设计人员足够的机会迁移程序,使他们不再使用即将退休的操作。另外,将移除的操作转向功能存根并返 回描述性的错误数据的技术也可以用于降低对不能进行及时迁移的服务消费者的影响。
- 更改现有操作的消息交换模式 **(MEP)** —— 修改操作的消息交换模式需要修改其输入和输出消息定义(还有可能需要其 fault 定义),这通常不是兼容性变更。若需要在保持向后兼容的前提下继续处理该 变更,那就需要在 WSDL 中增加新操作,让其使用更改后的 MEP。和重命名操作名一样,终止通告(Termination Notification,478)可用于辅助最终的转变。
- 为现有操作增加 Fault 消息 —— 为现有操作增加 fault 消息(当其独立于 MEP 的修改时)通常被视为兼容性操作,因为发出错误消息不会操作的核心功能。然而,因为它扩增了服务行为,应该只有当其作为一个新增操作的一部分而被增加时才能被当作一个兼容性变更。
- 增加新的端口类型(Port Type) —— 因为 WSDL 定义允许多个端口类型定义的存在,服务契约可以在现有端口类型的旁边新增端口类型。尽管这是一种兼容性变更,为该 Web 服务契约新增一个版本还是需要的。
- 增加新消息模式或属性 —— 为现有消息模式增加新元素或属性可以被看成是兼容性修改,前提是这些属性或元素是可选的(不是必须的)。这样,它们的存在不会影响到在它们新增之前已存在的服务消费者。
- 删除现有消息模式或元素 —— 不管它们是否是可选的或者必须的,若被使用中的消息模式元素或属性需要被删除,它都会导致非兼容变更。因此 ,该模式不能应用在该场景中。
- 修改现有消息模式的约束 —— 任何指定消息模式部分背后的校验逻辑的变更都可以看成是兼容性变更,前提是约束粒度变粗了。换句话说, 如果约束变松了,那么现有消费者不会受到影响。
- 增加新策略 —— 为现有策略附件文档中增加一个或多个 WS-policy 声明是兼容性修改,因为直接添加即可。
- 增加策略断言 —— 如果新增的策略断言是可选的,或者它作为可选的独立策略声明的一部分,那么新增该类策略断言就是兼容性变更。
- 增加可忽略策略断言 —— 由于可忽略策略断言通常用于表达服务的行为特征,所以这类变更不应该看成是兼容的。
注:这组变更对应于《针对 SOA 的 Web 服务契约设计和版本控制》(Web Service Contract Design and Versioning for SOA)的第 21,22,和 23 章,那里详细探讨了兼容性变更、非兼容性变更及相应代码示例。
影响
每当一个已发布服务契约被更改时,通过版本控制和治理相关的工作来确保变更能够代表契约的新版本,并向现有消费者正确表述和传达。如后面的关系一节中即将描述的,这将产生对规范版本控制( Canonical Versioning,286)及版本标识(Version Identification,427)的依赖。
当以这种方式应用兼容性变更时,会给服务契约带来冗余和重复(如应用一节所描述的),该模式最终会形成肿胀且难于维护的服务契约。而且 ,这些技术经常要求终止公告(Termination Notification,478)的陪伴,而这又为服务所有者和消费者带来了更多服务内容及治理的工作。
最后,该模式应用的结果让现有的服务约束更加放松(如在应用一节中修改现有消息模式的约束一段中描述的),它还会产生模糊的,粒度太粗的契约内容。
关系
为了在跨多个服务之间一致应用该模式,需要正式的版本系统存在,理想情况下这是由规范版本控制(Canonical Versioning,286)完成标准化工作的。再者,该模式依赖版本标识(Version Identification,472)来确保变更的合理描述。它也许还需要终止通告(Termination Notification,478)完成契约内容和消费者从旧服务到新服务的转换。
图 16.3
兼容性变更影响到的其他服务治理模式,但也依赖于其他一些服务契约设计模式。
案例分享 如反契约标准化(Contract Denormalization,414)中的案例分享中所描述的,FRC 的架构师最近对 Officer 服务契约进行了扩展,增加了 UpdateLog 操作。
在架构师将该服务发布到生产环境之前,它必须通过一个测试流程并获得质量保证团队的批准。该团队的第一个担心是,由于该变更已经修改了服务的技术接口,所以要进行回归测试以确保没有影响当前服务消费者。
架构师向 QA 部经理解释这些附加的测试是不必要的。他们解释道,UpdateLog 操作是仅仅附加到服务契约内容之后的,而且任何当前的契约代码都不会受到影响,正如下面实例中所强调的部分:
复制代码
<definitions name="Officer" targetNamespace="http://frc/officer/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:off="http://frc/officer/schema/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://frc/officer/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <types> <xsd:schema targetNamespace="http://frc/officer/"> <xsd:import namespace="http://frc/officer/schema/" schemaLocation="Officer.xsd"/> </xsd:schema> </types> <message name="UpdateOfficer"> <part name="RequestA" element="off:OfficerDoc"/> </message> <message name="UpdateOfficerConfirm"> <part name="ResponseA" element="off:ReturnCodeA"/> </message> <message name="UpdateOfficerLog"> <part name="RequestB" element="off:OfficerLog"/> </message> <message name="UpdateOfficerLogConfirm"> <part name="ResponseB" element="off:ReturnCodeB"/> </message> <portType name="OffInt"> <operation name="Update"> <input message="tns:UpdateOfficer"/> <output message="tns:UpdateOfficerConfirm"/> </operation> <operation name="UpdateLog"> <input message="tns:UpdateOfficerLog"/> <output message="tns:UpdateOfficerLogConfirm"/> </operation> </portType> ... </definitions>例 16.1
修改后的例 14.1 中 WSDL 文件,该例子展示了在应用反契约标准化(Contract Denormalization,414)过程中如何进行向后兼容性变更。 因为现有内容未被修改,且修改的仅仅是新增的内容,所以他们称契约是完全向后兼容的。QA 经理同意该说法,但仍然坚持某些测试以保证新增操作的逻辑不会影响服务的整体行为。
版本标识
由 David Orchard, Chris Riley 提供
如何让服务消费者感知服务契约的版本信息?
问题
当已发布的服务契约发生变更时,没有意识到该变更的消费者就会失去应变的机会,进而可能会受到变更的不利影响。
解决
附属于兼容性和非兼容性变更的版本控制信息可以作为服务契约的一部分进行描述,这既能用于通讯目的也能用于加强执行的目的。
应用
版本号可以被注入到 Web 服务契约的命名空间的值或注解中。
影响
该模式要求以合适的方式描述版本信息,这要求服务消费者设计人员事先理解这些描述。
原则
标准化服务契约
架构
服务
表 16.2 版本标识模式概览
问题
当对某契约进行兼容性或非兼容性变更时,任何对已发布内容的更改通常应该产生一个新契约版本。如果没有将契约版本和变更联系起来的方法,那么总存在服务与其当前及未来消费者之间的兼容性的风险,而且服务也更难被服务消费者设计人员发现(图 16.4)。
图 16.4
由于服务契约需要变更,位于左边的服务消费者对于它是否依然兼容一无所知。
再者,服务本身的管理和发展负担也越来越重。
解决
可以设计服务契约使其能描述版本标识,该标识就让服务消费者游刃有余地判断服务是否兼容。版本标识的使用还能进一步在版本控制上支持并发契约 (Concurrent Contracts,421),因此它能让消费者基于契约上的版本正确选择服务契约。如图 16.5 所示。
图 16.5
因为服务契约描述了版本信息,消费者 A 就会调用版本 3,因其设计上是与该版本兼容的。
应用
版本通常是由数字标识的,它经常被注入在服务契约中,或者作为可读性的注解,或者作为技术性契约内容的扩展。最常见的版本数字格式是小数形式 ,其整数部分作为大版本号,而小数部分作为小版本号。
版本数字的实际含义取决于高层版本策略的约定。这里描述了两种常见的方法:
- 工作量 —— 大版本号和小版本号用于指示每次变更所消耗的工作量,大版本号的增加意味着很多工作,而小版本号的增加代表较少工作。
- 兼容性保证 —— 大版本号和小版本号用于描述兼容性。最常见的规则是大版本号的增加意味着服务契约的一个非向后兼容性的改变,而小版本号 的改变则代表着相后兼容的变更,因此,小版本号的增加就意味着服务消费者不会受到影响。
注意,这两种版本标识系统可以一起使用,这样版本号的增加不仅可以描述兼容性和非兼容性的变更,还可以描述工作量的多少。
就 Web 服务契约而言,要保证当前服务消费者不会无意间绑定到某个已经对其做了非向后兼容性变更的服务,最常见的方法是在每次版本更新时把版本信息注入到新的命名空间值中。
注:尽管版本号经常被注入到 WSDL 文件的目标命名空间值中,而日期值则通常附加到 XML 模式定义的目标命名空间之后。参考《针对 SOA 的 Web 服务契约设计和版本控制》的第 20,21 和 22 章了解更多信息及代码样例。
影响
版本标识系统和惯例通常是特定于某服务目录之内的,并往往是由规范版本控制(Canonical Versioning,286)制定的标准化版本控制策略的一部分。其结果是,它们并不是企业级的标准,所以将其描述技术契约中时,这就会不断给服务消费者强加一个需求,要求他们理解版本标识的含义并自动消费它们。
当服务向新消费者或外部消费者暴露时,也存在相同的需求,但可能很难实现必要的标准实施。
关系
该模式通常与规范版本控制(Canonical Versioning,286)一起应用(或作为起其结果被应用),它也是实施兼容性变更(Compatible Change,465)的核心部分。
图 16.6 版本标识主要关系到其他契约版本控制模式
注:当将版本号作为消息的一部分时,版本标识等同于格式指示器(Hohpe,Woolf)。格式指示器不同于版本标识的地方是它以消息为中心,并且它还能表达其他元数据信息,比如外键和文档格式等。
案例分享 如兼容性变更( Compatible Change,465)中所描述的案例,QA 团队要对 Officer 服务的扩展版进行进一步的测试,在测试之后他们拒绝了新服务向产品的发布,基于一下原因:服务契约必须包含版本号,以表示做过变更。
版本号要依据现有的版本控制习惯,向后兼容变更应增加小版本号(小数点之后的小数部分)。FRC 架构师认为这是一个好建议,因此很快通过“documentation”元素为 Officer 服务的 WSDL 加了一个可读的注视。如下所示:
复制代码
<definitions name="Officer" targetNamespace="http://frc/officer/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:off="http://frc/officer/schema/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://frc/officer/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <documentation>Version 1.1</documentation> ... </definitions>例 16.2
为例 16.1 增加版本号之后的 WSDL 文件。 在 1.1 版的 Officer 服务部署几个月后,两个新项目启动了,都属于 HR 正在使用的系统。为了确保设计标准的遵循,某个 FRC 的企业架构师加入到这两个项目中。在审阅完两个设计说明书后,她发现了一些共性。第一个方案需要对事件做日志的功能,而第二个方案需要对错误做日志的功能。她很快意识到需要为 FRC 创建一个独立的日志服务。在她的建议提出之后,Logging 服务就被开发出来。几个星期后的一次服务目录蓝图评审会中,一位分析员指出 Officer 服务中之前加入的 UpdateLog 操作的功能应该被包含在新增的 Logging 服务之内。
FRC 架构师团队同意做这个更改,尽管不认为需要立即做。再几个星期之后,对 Officer 服务做了修改,结果,原先 UpdateLog 的逻辑移走了,而该操作本身从契约里删除了。
根据质保团队设定的版本惯例,这类修改属于“非兼容性的”,即执行了一个非向后兼容性变更,将影响到已经依赖于 UpdateLog 操作的现有消费者程序。 相应地,他们需要增加大版本号(整数部分)并进一步将此版本号附加在 WSDL 文件的目标命名空间中,如下所示:
复制代码
<definitions name="Officer" targetNamespace="http://frc/officer/wsdl/v2" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:off="http://frc/officer/schema/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://frc/officer/wsdl/v2" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <documentation>Version 2.0</documentation> <types> <xsd:schema targetNamespace="http://frc/officer/"> <xsd:import namespace="http://frc/officer/schema/" schemaLocation="Officer.xsd"/> </xsd:schema> </types> <message name="UpdateOfficer"> <part name="RequestA" element="off:OfficerDoc"/> </message> <message name="UpdateOfficerConfirm"> <part name="ResponseA" element="off:ReturnCodeA"/> </message> <portType name="OffInt"> <operation name="Update"> <input message="tns:UpdateOfficer"/> <output message="tns:UpdateOfficerConfirm"/> </operation> </portType> ... </definitions>例 16.3
UpdateLog 操作被移除之后的 Officer 服务的 WSDL 文件,因为是非兼容性变更,所以要求使用新目标命名空间。
终止通告
由 David Orchard、Chris Riley 提供
如何向消费者程序传达预订的服务契约退休的通知。
问题
消费者程序可能没有意识到服务或服务契约版本即将退休,所以冒有运行时失败的风险。
解决
设计服务契约,使之能够向程序和人们传达服务终止的信息。
应用
对服务契约进行扩展,增加可忽视性策略断言或附加的人可读的注解。.
影响
用于表达终止信息的语法和习惯必须能被服务消费者理解才能有效地使用该信息。
原则
标准化服务契约
架构
组装,服务
表 16.3
终止通告模式概览
问题
由于服务的不断发展,各种条件和情况都可能导致服务契约的退休,可能是其一部分,也可能是整个服务本身。
这包含以下情况:
- 服务契约面临非向后兼容性变更
- 虽然是兼容性变更,但是严格的版本控制策略要求使用全新的版本。
- 由于业务的变化,服务的原始功能不再可行
- 服务被分解成多个小服务,或者合并成另一个服务。
在大型 IT 企业中,特别是将服务暴露伙伴组织使用的企业,在服务或其某部分即将终止时及时通知消费者程序所有者是非常困难的事情。
没能察觉到预先安排的服务终止将不可避免地导致运行时失败,没有意识到该情况的服务消费者程序在调用该服务时当然会遭到拒绝(图 16.7)
图 16.7
今天(today),与往常一样,服务消费者程序(右)能够通过契约调用某服务,但是某天(tomorrow)该契约终止了,调用就会失败。
解决
用细节的终止信息包装服务契约,因此让服务消费者能够提前意识到服务的退休(图 16.8)。
图 16.8
服务契约包含一个标准的申明,由于它传递了约定的退休时间。这样,服务消费者就不会在契约终止之后继续尝试调用该服务。
应用
对于普通的技术性契约内容,该模式常用来为其增加用户可读的注解来注明过期时间。然而,对于 Web 服务契约,还可以使用 WS-Policy 通过可忽略的策略断言描述终止通告。这样,客户端程序就可以通过程序的方式自动校验该终止信息。
值得一提的是,除了描述服务契约的终止外,该模式还可用于其他目的,如:
- 指示某个功能或操作的终止 —— 这对于使用兼容性变更(Compatible Change,465)中所描述的迁移技术特别有用,比如继续保留某个原始操作,同时增加一个相似的新操作的场景。
- 指示整个服务的终止 —— 当整个服务程序本身即将终止时,也可以用相同的方法传达该通知。
- 指示消息模式的终止 —— 虽然策略断言不适合该目的,但可以在模式中添加正则注解,由其解释何时该模式版本将终止或被替换。
注意,相关的治理标准可以作为上层规范版本控制(Canonical Versioning,286)策略的一部分,使其通过标准注解或不可忽略的策略断言的方式来表达终止信息。后者需要 Web 服务契约保护终止断言,不论该 服务会不会被终止。对于那些不会终止的契约,可以在填日期值的地方填入预定义的值(或留空)。
影响
该模式描述的所有技术都需要使用非标准的服务契约扩展内容。因为目前描述终止信息尚无任何标准。终止通告依赖于管理标准的存在及其成功执行, 所以直接依赖于规范版本控制(Canonical Versioning,286)。
关系
正如刚刚提到的,该模式的应用往往受到规范版本控制(Canonical Versioning,286)的管辖。兼容性变更(Compatible Change,465)和代理功能(Proxy Capability,497)都可能会依赖于终止通告。
图 16.9
终止通告主要关系到其他版本控制模式,但也可能支持代理功能(Proxy Capability,497)。
注: 终止通告在概念上类似于消息终止(Hohpe,Woolf),该模式允许为消息增加用于指示消息失效时间的时间戳。
案例分享
在版本标识(Version Identification,472)的例子中,FRC 团队从 Officer 服务的 WSDL 文件中删除了 UpdateLog 操作,这产生了一个非向后兼 容性变更。在与由此变更所影响的消费者程序的管理员会议沟通之后,FRC 架构师团队开始意识到该变更为消费端程序带来很大工作量,并且需要好几个月才能让 所有消费者程序迁移到新部署的 Logging 服务。此外,好几个消费者程序负责人都不在场,只能在事后告知他们该变更信息。
由于这些情况,FRC 团队决定推迟变更,在 Officer 服务的 Logging 操作可用的情况下,容许 UpdateLog 操作继续保留 6 个月。他们和质保部门一其制定了一个适应该转变的计划,如下:
- 建立新设计标准,不允许新开发的服务消费者程序继续访问 Officer 服务的 UpdateLog 操作。
- 通过邮件通知所有团队领导,告知 Update 操作即将在何时删除。
- 通过可忽略性策略断言在 Officer 服务的 WSDL 文件中注入该终止日期。
其中第三步 3 的实现如下:
复制代码
<definitions name="Officer" ... > ... <binding name="bdPO" type="tns:OffInt"> <operation name="Update"> <soapbind:operation soapAction="http://frc/update/request" soapActionRequired="true" required="true"/> <input> <soapbind:body use="literal"/> </input> <output> <soapbind:body use="literal"/> </output> </operation> <operation name="UpdateLog"> <wsp:Policy> <pol:termination wsp:Ignorable="true"> Mar-01-2009 </pol:termination> </wsp:Policy> <soapbind:operation soapAction="http://frc/updateLog/request" soapActionRequired="true" required="true"/> <input> <soapbind:body use="literal"/> </input> <output> <soapbind:body use="literal"/> </output> </operation> ... </binding> ... </definitions>例 16.4 对 Officer 服务的 WSDL 文件中的 binding 结构中的操作元素进行更改,使其包含一个客户化 WS-Policy 断言,用来描述 UpdageLog 操作的终止时间
注:欲了解更多关于终止通告的示例请参考《针对 SOA 的 Web 服务契约设计和版本控制》中的第 23 章。
服务重构
如何在不影响现有服务消费者的前提下发展服务?
问题
服务的逻辑和实现技术可能会随着时间的迁移而过时,然而服务可能已经根深蒂固以至于很难被替换。
解决
服务契约应被保留,用以维护现有消费者的依赖关系,而服务的底层服务逻辑和实现可以被重构。
应用
服务逻辑和实现技术不断发展或更新,但必须要通过更多的测试与验证。
影响
该模式带来了治理工作,也可能会受到新技术或逻辑带来的不利副作用的影响。
原则
标准化服务契约,服务松耦合,服务抽象
架构
服务
表 16.4
服务重构模式概览
问题
服务在最初交付之后,未能预见的性能及业务需求可能会超出服务所提供的能力(图 16.10)。替换整个服务的做法是不可取的,特别是有很多消费者程序与当前服务契约已经形成依赖关系的情况。
图 16.10
现有服务的消费者提出的新需求在服务的最初设计中并无覆盖。红色图标表示该服务架构的不同部分,它们可能是独立进行版本控制的。
解决
软件重构是广为接受的软件工程实践,现有软件程序可以逐步改进而不影响其外部行为。当应用于服务的设计时,该方法在不影响现有消费者的前提下为服务的发展提供了更多的机会。如图 16.11 所示,应用该模式后,在保留服务契约的同时,服务的底层逻辑和实现可以进行定期优化、改进、甚至升级。
图 16.11
只要经过抽象,该服务架构的所有部分都可以在不破坏现有消费者关系的同时进行重构,并且服务契约和面向外部的消息处理代理不会受到重构的影响。
应用
软件重构的实践允许程序在保留其接口和整体行为的同时通过一系列微小的升级实现改进。通过限制升级的范围,就可以最小化服务消费者受到的影响。软件重构技术关注的重点是这些重构步骤的累积效应。
当服务已经应用了契约解耦(Decoupled Contract,401)和服务松耦合设计原则之后应用该模式会更有效。将服务逻辑从完全解耦的契约中分离出来增加了服务重构的自由度,同时还能把对现有服务消费者的影响降到最低。
注: 关于重构技术以及专用模式的图书有很多。其中比较著名的是《重构:改进现有代码的设计》(Refactoring: Improving the Design of Existing Code,由 Fowler,Beck,Brant,Opdyke 及 Roberts 合著)和《重构模式》(Refactoring to Patterns,Kerievsky 编著),两本书都由 Addison-Wesley 出版。该网站 http://www.refactoring.com 提供了更多资源以及经验证的“重构”技术。
影响
对现有服务逻辑或技术的重构需要服务的再设计,再开发以及再测试等环节,才能保证服务契约中现有的能力(包括其 SOA 能力)能够继续(或更好地 )服务于消费者。
由于应用该模式时修改或替换了现有的已形成或以验证的逻辑及技术,所以仍然存在更改后的服务或功能在某种程度上对现有消费者有不利影响的风险。至于该风险可以减轻多少,它与新加的逻辑及技术的成熟度,适应性和范围成正比,也受到对修改服务的质量保证以及测试深度的影响。
关系
服务重构可以被应用到什么深度取决于服务本身的最初设计,这是为什么它与服务标准化(Service Normalization,131),契约集中(Contract Centralization,409)以及契约解耦(Decoupled Contract,401)等有着直接的关系。该模式的成功应用所获得的抽象以及独立性允许服务在个别管理和发展的过程中减少对消费者程序的影响。
此外,由于重构需求的自然特点,为了适应服务的变更,可能还需要服务分解(Service Decomposition,489),并发契约(Concurrent Contracts,421)和服务门面(Service Fasade,333)等模式一起使用。
图 16.12
服务重构依赖于好几个关键的契约相关的模式,才能保证重构相关的变更不会破坏现有服务消费者。
案例分享 Alleywood 的 Employee 服务实现的完成已有一段时间,最初,该服务建立了标准的服务契约并作为访问大型 ERP 系统的 HR 模块的入口点。由于 McPherson 的收购,为了跟进其整体 IT 规划,很多产品线需要升级或替换。 ERP 系统作为该项目的一部分被重新评估。 ERP 的提供商被一个有竞争力的软件制造商收购,该 ERP 平台成为其更大产品线的一部分,并由该产品线提供另外的 ERP。McPherson 集团认为原有的 Alleywood 的 ERP 环境不久将被停用,以为其新 ERP 系统让出市场占有率。
最后决定全盘替换该产品。这自然会影响到很多服务,包括 Employee 服务。然而,因为其服务契约是解耦的,并且完全符合标准化,所以它不会依赖于底层 ERP 环境的任何部件。
他们引进了一款新 HR 产品及定制开发的员工报表系统,这样开发者就能重构某些核心服务逻辑,从而即可以提高较受欢迎的服务功能的并发使用的阈值,有可以保留原有的服务契约及预期的整体服务行为。
这就将 HR 产品造成的影响仅仅限制在服务上。除了短暂的不可用外,所有 Employee 服务的消费者都不会受到影响,并且可以继续像往常一样使用服务。
服务分解
如何在服务实现之后增加服务粒度?
问题
粒度太粗的服务会阻碍组合服务的最佳设计。
解决
事先实现的粗粒度服务可以被分解成两个或多个细粒度服务。
应用
重新改造底层服务逻辑,创建新服务契约。该模式很可能需要代理功能(Proxy Capability,497)来保留原始粗粒度服务契约的完整性。
影响
细粒度服务的增加自然会导致更大,更复杂的服务组合的设计。
原则
服务松耦合,服务组合
架构
服务
表 16.5
服务分解模式概览
问题
在最初的分析并进行服务建模阶段考虑其实用性是当然的。比如,理想情况下可能的一组候选细粒度服务可能后来被结合成几个粗粒度的服务,主要是基于性能的考虑或者其他基础设施相关考虑,要求保证服务组合大小的可控性。
在服务目录架构成熟以及更强大且精细的技术和运行时产品引进之后,更大更复杂的服务组合就成为现实。当设计这样的组合时,通常仅选择必须的能力去自动完成指定的父业务,首选的做法是使每个独立服务的触角越小越好。然而,当必须要围绕一些粒度太粗的的服务工作时,可能会给组合服务的性能带来负面影响,而且整体组合设计也可能不会达到最有(图 16.13)。
图 16.13
一个带有功能上下文的 Invoice 服务最初从三个独立的业务实体衍生而来,最后发展成为一个巨大的程序并且拥有很长的触角,不论组合服务要组合的能力是什么都得从整个大服务开始。
注:该问题发生的另一种情况是服务开发采用的中间汇合(meet-in-the-middle)方式,且自顶向下的分析在开发之前仅仅完成了一部分。在这种交付方式中,自顶向下的的流程和服务交付项目同时进行。当自动向下的分析达到有必要对最初标识的服务目录进行更新的点时,就应该有义务重新修正已实施的服务设计。请参考《面向服务架构:概念,技术和设计》(Service-Oriented Architecture: Concepts, Technology, and Design)一书的第 10 章。
解决
将粗粒度服务分解成一组细粒度服务,从总体上代表原服务的功能上下文,但建立每个小服务自己的功能上下文,如(如 16.14)。
图 16.14
原先的粗粒度 Invoice 服务被分解成 3 个独立的服务,让每一个服务继续关联通用的 Invoice 处理但所封装的功能是原有功能的子集。
应用
实施该模式主要是对现有粗粒度服务进行拆分,将其逻辑重组到更新,更细的功能边界内。
因此,第一步通常是重返服务目录蓝图并决定怎样将服务重新建模成多个候选服务。该过程的一部分是定义新候选服务,特别是当最初的服务设计过程中没有考虑分解能力(Decomposed Capability,504)的情况。建模完成后,新服务进入标准的生命周期中,从契约设计开始(基于建模的候选服务),然后继续其他步骤直到测试和质量保证的环节(图 16.15)。
图 16.15
每个新定义的细粒度服务提供较少的功能,所以程序规模也较小。
如果还决定翻新以前已与原服务建立依赖关系的消费者程序,还需要代理功能(Proxy Capability,497)为向后兼容保留原有的服务契约。
注:该模式背后的概念可以被反向地应用,也就是两个或更多的细粒度服务组合成一个粗粒度服务的情况。使用代理功能(Proxy Capability,497)仍然能够用于保留原有服务契约。这是服务合并(Service Consolidation)的基础,在本书编写时,该模式正作为一个候选服务在接受 SOAPattterns.org 的审阅。
影响
服务分解到什么程度会破坏服务目录?这取决于服务是如何建立的,以及有多少消费者程序已经与其建立关系。越多消费者程序,该模式就可能更复杂,更具破坏性。
由于该模式通常在目录架构成熟后才应用,所以使用时要连同不断的代理功能(Proxy Capability,497)一起仔细规划。
预防性地使用分解能力(Decomposed Capability,504)可以缓解服务分解的不利影响,而且还能使功能性服务契约的分离的更干净。
关系
服务分解与其他服务级模式之间有着一系列关系,最明显的是服务重构(Service Refactoring,484)。当服务因为重构工作而升级时,服务分解可能是一种很好的实现方式。
如代理功能(Proxy Capability,497)中所描述的,服务分解依赖它通过再开发将一个或多个功能转换成代理而实现真正的分解。如此,该模式与代理功能 (Proxy Capability,497)共享好几个模式。
服 务分解常应用于未知的服务,因此常在实体抽象(Entity Abstraction,175)及效用抽象(168)中尝试它。然而,由于需要代理功能(Proxy Capability, 497)在某种程序上进行反服务标准化(Service Normalization,131),所以该模式应用的结果会带来某种程度的服务冗余。
图 16.16
服务分解是用于拆分服务逻辑的重构方面的方法,它与很多为服务逻辑及契约塑形的模式都有关联。
案例分享 服务重构(Service Refactoring,484)中的案例解释了 Employee 服务由于一系列原因被重新开发。其结果之一是现在服务更具延伸性且能够处理更多的并发使用负载。强调延伸性的主要原因是即将来临的服务组合将要访问 Employee 的数据和功能,当时服务组合正在计划阶段,而现在它们已处于生产阶段。
初步统计显示,尽管使用阈值增加了,对 Employee 服务的访问压力还是过大,而且,已经出现一些抱怨——当该服务的调用作为整体组合的一部分时,该服务造成延迟以及内存暴涨等现象。
最初,负责 Employee 服务的团队考虑通过冗余实现(Redundant Implementation,345)的方式来减轻压力,然而该方法可能会解决延迟问题,但不会解决内存暴涨的问题。
该团队随后考虑将 Employee 服务的功能分解成两个独立服务的情况。从后台看,存在一个这样做的相对清晰的方式。目前,服务包装的功能来自 HR ERP 系统和定制开发的报表系统。然而,该服务作为实体服务层的一员,参与的架构师和业务分析员希望在即将拆分的两个服务中都保留业务实体相关的功能上下文。因此,他们不愿意仅基于当前服务的实现层做决定。
他们向负责维护主实体关系图的信息架构组寻找与 Employee 相关的合适实体,期望从这些实体中能够形成服务拆分的基础。他们发现一个 Employee Records 实体与其父实体 Empolyee 之间存在关系。Employee Records 实体描述了员工(Employee)的历史信息,如加班、病假、抱怨,升迁、伤害等。
该团队检查了当前实体服务的功能及需要增加的附加功能(如那些已在服务目录蓝图里描述,但尚未实现的),又检查了后台被封装的系统。他们发现定制开发的报表系统不能提供 Employee Records 处理的所有能力, 所以该服务应该继续访问 HR ERP 系统,而且,最终 Eployee Records 服务将用于在今后访问中心数据仓库。
令人高兴的一点是,他们最初的访问统计显示某些来自 Employee 服务的延迟问题是由于执行耗时的报表查询导致的。如果这类功能可以放在一个单独的服务中实现,那么主要的 Employee 功能就可能更加可延伸且可靠,而且 Employee 服务也将更“轻”并能更加有效地参与组合。
综合所有这些因素,该团对认为将历史报表功能剥离到一个独立的服务(如称之为“Employee Records”)中的做法是合理的。他们所面临的第一个挑战是该服务契约已经被很多消费者程序使用。如果他们将该服务的能力转移到另一个服务中,就将带来严重的破坏。在这种情况下,他们使用了代理功能(Proxy Capability,497),这将在下一节的案例分享中描述。
注:前文描述了如果分解服务的一种方案。另一重设计方案从更注重效用的角度考虑,将一个实体服务拆分成一个实体和一个工具服务。不过, 服务应该如何分解最好应进行详尽的分析,以确保能完全满足具体的业务需求。
代理功能
如何让被分解的服务继续向受分解影响的服务消费者提供服务?
问题
如果需要将某服务分解成多个服务,那么其服务契约及现有消费者都会受到影响。
解决
尽管底层功能已被分解,还能将现有功能定义转化成代理,从而保留原有的服务契约。
应用
引入 Facade 逻辑在代理和新生成的服务之间传递请求及响应。
影响
该模式的实际应用会导致一定程度的服务非标准化。
原则
服务松耦合
架构
服务
表 16.6
代理功能模式概览
问题
基于服务分解(Service Decomposition,489)的原则,有时的确需要进一步分解某服务的功能边界,使之形成两个或多个小功能,实质上在服务目录中创建了新服务。这很明显对已经与原有服务契约形成依赖的现有服务消费者有不利影响(图 16.17)。
图 16.17
移除现有服务契约中的一部分功能将对现有服务消费者产生可预见的不利影响。
解决
在保留受分解影响的功能的同时,允许相同的功能作为新服务的一部分存在。尽管服务的原有上下文已经改变,其正式功能边界也缩小了,它还继续提供不属于其契约和功能和边界之内的能力。这就是代理功能模式所提供的功能,(通常在一段有限的时间内)缓解分解对服务目录造成的影响 (图 16.18)。
图 16.18
通过保留现有能力并使其作为新建服务逻辑的代理,现有服务消费者所受的影响就会减少。
这不会破坏对新服务功能的独立访问。事实上,提倡通过新服务契约对功能逻辑进行访问,因为这样能够最小化代理功能的最终退出所付出的代价。
应用
代理功能依赖于服务门面的应用(Service Facade,333),因为需要通过门面的创建来保持受影响的服务功能。唯一的区别是,门面不再调用原有服务的功能,而调用新服务的功能(图 16.19)。
图 16.19
当现有服务访问 Invoice 服务的某操作时,由于服务的分解(1),该功能已被移走,新加的门面组件将请求消息传递到该功能的新位置(2),即图中的 Invoide Reporting 服务。
注:为了传达预订的代理功能的退休,它经常与终止通告(Termination Notification,487)联合使用。
影响
尽管该模式的应用在延伸服务契约寿命的同时允许服务逻辑的分解,它还是会引入一定程度的服务非标准化,而这与服务标准化(Service Normalizatio, 131)的目标是相违背的。
代理的功能应该使用元数据进行清晰地标注,以指明它们不再代表正式的服务端点,从而避免消费者对它们进行无意的绑定。
此外,仅该模式本身无法保证代理功能继续提供被替代的原服务所提供的行为和可靠性。
关系
分布式功能(Distributed Capability,510)为最终进行服务分解(Service Decomposition,498)做好准备,而代理功能则实际上在保留原有服务契约的同时实现了分解。
该模式受契约解耦(Decoupled Contract,401)的支持,它既允许原服务契约的存在,也允许分解的服务拥有支持代理功能的独立定制契约。服务门面 (Service Facade,333)也是一个必要的角色,它(作为代理)为新分解的服务传递请求和响应。
另外,如前文已经提及,该模式的确会违背服务标准化(Service Normalization,131)的目标。单从服务端点的角度看,该模式造成功能的冗余,为了支持服务发展,该代价是可接受的。
图 16.20
为了支持新服务的创建,代理功能修改了服务的结构,所以它影响到服务逻辑结构和服务分解过程相关的若干模式。
案例分享
在本案例中,我们将阐述 Alleywood 团队如何形成将现有 Employee 服务分解成 Employee 和 Employee Record 两个服务的决定。
从头开始,他们需要找到有效的方法进行:
- 创建新的 Employee Record 服务。
- 将 Employee 服务中的相应功能转移到该新建服务中。
- 在不修改 Employee 服务契约的前提下完成第 1,2 步,其目的是为了保证现有服务消费者不受影响。
为了完成第 1 步,他们为 Employee Record 建模,如图 16.21 所示。
图 16.21
经过分析,为候选服务 Employee Record 建模了四个候选功能。为完成第 2、3 步,他们为 Employee 服务中的每个将转移到 Employee Record 服务中功能运用代理功能,图 16.22 描述了如何将 Employee 服务中的两个原有功能映射成 Employee Record 中的四个功能。
图 16.22
Employee 服务的 AddYearRecord 和 GetHistory 方法被定位到 Employee Record 服务的 Add,GetRecordReport,GetHoursReport 和 GetEvalReport 等方法的代理。最终设计并交付的 Employee Record 服务是一个功能完全的并且独立的服务。然而,Employee 服务契约没有改变,并且以门面组件的形式新增了一些逻辑。该功能负责对原有的 AddRecord 和 GetHistory 方法的请求,然后将请求向 Employee Record 传递,接受到其响应后再转发给 Employee 服务的消费者。
不过,仍然有一个问题。为了使 GetHistory 操作运行,它必须要向 Employee Record 服务进行三次请求(对每个 Employee Record 的 GetReport 操作请求一次)。
该团队考虑是否要为 Employee Record 服务增加一个 GetHistory 操作作为 Employee 服务必须要做的工作的代理。但是,他们又担心附加的操作会迷惑其他服务消费者。最终他们只好决定加速 Employee 服务的 GetHistory 操作的退休过程。
分解的功能
如何设计服务使对其进行功能逻辑分解的可能性最小化。
问题
在服务实现之后对服务进行分解需要其功能逻辑的分解,这可能会导致毁灭性的破坏,对服务契约的维护带来问题。
解决
使用一组易于分解的细粒度功能装配服务,形成的服务更容易在将来进行分解。
应用
通过附加的服务建模工作定义粒度更细,更易于分布的功能。
影响
由于代理功能的支持,服务在被最终分解之前,都可能伴随着“浮肿的”服务契约而存在。
原则
标准服务契约,服务抽象
架构
服务
表 16.7
分解的功能模式概览
问题
某些类型的服务在开发部署后更易于被拆分。比如某些实体服务,它们的功能语义来自于相应的业务实体,而这些业务实体可能又作为通用信息架构规范的一部分。这样,往往会造成某些实体服务的语义从一开始就成为很多更大更复杂的业务实体(甚至一组相关实体)的基础。
这对于眼前的需求来说是足够的,但是最终它会导致一系列问题(如图 16.23),包括:
- 因为很多功能与其功能语义相关,所以在其之上增加了很多附加功能,造成了服务的扩展,导致了臃肿的服务功能边界,难以治理。
- 由于许多附加功能的加入或这某些个别功能的高重用率,服务越来越受欢迎,使得服务成为性能瓶颈。
图 16.23
衍生自一组 Invoice 相关的业务实体(左图)的 Invoice 实体服务(中间)暴露了一组粗粒度的功能,当有服务分解需求时,这些功能难于分解。只有拆分每个受影响的 Invoice 服务功能,才能满足新服务的需要(右图)。
尽管事先可能会预料到这些问题,然而可能由于当时的基础设施限制了潜在服务组合的规模,在一开始就创建一组细粒度的服务是不可能的。有时企业要等到其基础架构升级或其提供商的运行平台成熟,并于能够支持由多个服务参与复杂的组合时。然而同时,企业又不能推迟服务的交付。
解决
最初的服务设计中就考虑将来的分解需求,通常结果是创建更细粒度的功能。例如,使实体服务更加向个体业务实体对齐就可以形成细粒度的功能,这样,如果将来 该服务要被分解成一组细粒度的服务(用来分别代表个体业务实体),就不需要太多的功能分解就可以实现该转换(图 16.24)。
图 16.24
图 16.23 中由相同的业务实体(左图)衍生而来的 Invoice 服务(中间)现在向外暴露了一组粒度更细的功能,其中一些与特定的业务实体相关,这就使得后续的服务分解更容易实现。分解后的服务(右图)不再冲突,原因是受分解影响的功能可以清晰地映射到新服务上。而那些相同的功能还在通过代理功能(Proxy Capablility,497)留在原有的 Invoice 服务契约(右上)中。
应用
由于要确定合适的服务功能定义,该模式的引入增加了初期的服务建模工作。具体而言,需要考虑以下方面:
- 当前功能范围如何能潜在地被分解成两个或多个功能语义。
- 如何为这些新功能语义定义功能。
该建模工作遵循一个流程并在该服务范围内定义一组候选服务。这些候选服务代表着当前服务将来分解后可能产生的服务,它们可转换成(便于今后进行分解的)候选功能。
注:该模式不同于反契约标准化(Contract Denormalization,414),因为后者为了支持消费者需求而引入了冗余和细粒度的功能。而该模式的目标是支持长远的服务发展和服务目录的整体需求,产生的是目标粒度的功能(可能冗余,可能不冗余)。
影响
应用了该模式的最初服务契约可能会很大且难用。附加的细粒度功能可能会导致服务消费者(需要通过多次调用一组细粒度功能才能完成原本捆绑在一起的粗粒度服务)的间接性能开销。这就可能需要应用反契约标准化(Contract Denormalization,414)来产生更多的功能。
即使在服务分解之后,原始服务的现有消费者还需要原始的服务契约保留一段时间,需要通过代理功能(Proxy Capability,497)在中间协调。
此外,在最初定义服务的时候是很难预计到服务将来被如何分解,而且总会有这样的风险存在:功能划分很细的服务最终未被分解,却同时给消费者带来了不必要的性能负担。
关系
图 16.25 中描述的核心关系是分解的功能(Decomposed Capability)和服务分解(Service Decomposition,489)之间的关系,因为应用该模式就需要预先考虑服务将来被分解,因此,它还可以被看成是一种治理模式,其目标是减少服务发展的影响。基于相同的原因,它还关系到代理功能(Proxy Capability,497),因为可能会对被分解的一个或多个功能应用此模式。
图 16.25
分解的功能模式为最终的分解准备服务契约,使之与服务分解(Service Decomposition,489)有着密切的关系。
如前文所述,该模式产生的粒度更细的功能可能要求反契约标准化(Contract Denormalization,414)的应用。
案例分享 代理功能(Proxy Capability,497)中的案例分享一节中展示了即使为现有服务消费者建立用于代理的功能,服务的分解还是会导致后续的设计问题。如果新产生服务功能不能和原有服务的功能语义及功能粒度完全匹配的话,代理映射的无能为力和尴尬就会出现。取决于旧功能退休的时间,服务的分解可能实际上会增加某些(原本它期望改进的)功能的负担。
让我们再回到在前文例子中的 Employee 服务和 Emploee Record 服务上。如果我们回溯到最初为 Employee 服务建模的时刻,我们可以向负责定义原始候选服务的架构师和分析员提供机会,让他们在进入下一步物理的服务设计和实现之前应用分解的功能模式。
就 Alleywood 的例子而言,服务可能已经基于两个已经讨论过的业务实体(Emploee 和 Employee Record)以及第三个现有 Employee 相关的业务实体,它被称为服务分类。这些实体也许从一开始就确定了由最初的 Employee 实体服务作为三个实体服务捆绑在一起的整体出现。
该服务的功能定义时也许已经考虑到未来的分解,而且其结果看起来可能更像图 16.26 这样:
图 16.26
原始的 Employee 服务的建模更适合将来的分解,它所包含的一些功能直接关联到与 Employee 相关的已知的业务实体。要注意这些功能的名字不一定要与他们在将来被分解后的名字相同。
分布式功能
服务如何在保留其功能语义的同时完成特殊的处理需求?
问题
对属于服务内的某个功能可能有特别的处理需求,而缺省的服务实现不能满足该需求,但分离服务的功能逻辑会破坏服务上下文的一致性。
解决
底层的服务逻辑是分布的,因此可以在物理上分离有特殊处理需求的功能的实现逻辑,还可以同时继续以相同的服务契约出现。
应用
移出一部分逻辑,并增加中间的处理环节,由它作为连接被移除的逻辑及主服务逻辑中间的纽带。
影响
某功能逻辑的分发会产生远程连接及新产生的中间处理环节相关的性能开销。
原则
标准化服务契约,服务自治
架构
服务
表 16.8
分布式功能模式概览
问题
服务功能上下文内的每个功能都代表这一部分处理逻辑。当服务以一种物理实现形式而存在时,其周围环境不一定能完全支持所有的相关功能的处理需求。
比如,可能对某个功能有特殊的性能、安全、可用性或可靠性方面的需求,而这些需求只能通过特殊的架构扩展和基础设施才能实现。又或者,由于某个特定功能的处理需求的增加导致了服务整体实现的扩展,从而破坏了其他功能的性能及可靠性(图 16.27)。
图 16.27
Invoice Web 服务的 Consolidate 操作经常受到高并发使用及长响应时间(在进行复杂的综合计算时)的影响,这些因素有规律地查找服务器资源,从而破坏其他服务操作的性能及可靠性。
支持该功能的逻辑能够被拆分到其独立的服务实现中。然而,这可能会连带导致原有服务建模的功能上下文的分解。
解决
将具有特殊处理需求的功能逻辑分布到一个物理的远程环境,添加中间处理逻辑代表独立的服务契约在本地及分布的服务逻辑之间交互(图 16.28)。
图 16.28
Consolidate 操作的逻辑被移植到一个单独的物理环境。由服务门面组件代替 Invoice 服务契约与 Consolidation 的逻辑进行交互。
应用
该模式的实现通常依赖于通过服务门面(Service Facade,333)的应用,由其建立中间逻辑,作为“组件合成”的控制器。代表分布式功能逻辑的组件通过远程访问与门面的进行交互。
在某种程度上性能需求可以通过在门面组件中嵌入附加的处理逻辑实现流水化,使之不仅仅完成转发请求和响应消息的功能。例如,门面逻辑中可以包含一些程序进一步从进入的请求消息中解析及抽取数据,使得被传送的信息仅是分布的功能逻辑所需要的。
服务门面(Service Facade,333)的另一种选择是服务代理(Service Agent,543)。它可以为特定的服务功能开发由事件驱动的代理,由它们完成相应契约内的校验(或者可能验证延迟到功能逻辑本身),然后简单地将消息直接转发给相应的功能,同一个代理也可以处理从功能逻辑发出来的消息。
影响
该模式以一定的性能开销为代价,却保留了服务功能上下文的单纯性。把服务契约所在的位置作为多个分布式功能的唯一访问点增加了(不论何时访问服务时)远程访问的机会。
如果通过分离功能逻辑才能保证高流量时的响应时间,那么该目标在一定程度上会受到远程访问的牵制。而另一方面,它有助于整体的服务自治,因为功能分离有助于提高被分离功能逻辑的自治级别。
关系
当构建支持分布处理的服务时,服务实现的本身就如同一个微型复合,不过这里的门面组件既担当了组件控制器的角色,又作为分布式服务逻辑的唯一访问点。这解释了为什么该模式与服务门面(Service Facade,333)有着密切联系,也解释了为什么契约解耦(Decoupled Contract,401)特别支持该模式。
契约集中(Contract Centralization,409)也是服务设计的重要部分,因为不论底层服务逻辑如何分布,它都保证服务契约作为服务的唯一访问点。
当分布式功能需要贡献给对服务相关数据的访问,服务数据复制(Service Data Replication,350)可以用来辅助这种访问,而不需要引入服务内部的数据共享问题。另外,该模式往往是由实施服务重构引入的,所以它可以被看成是重构的后续,特别是当它应用在服务的最初部署之后。
图 16.29
分布式功能支持服务逻辑的内部分解,因此与服务逻辑及契约相关的模式有存在关联。
案例分享 新部署的 Employee Record 服务是在应用服务分解(Service Decomposition,489)和代理功能(Proxy Capability,497)之后定义的(参考相应案例分享部分的介绍),该服务越来越受欢迎。现在,它已经被 8 个复合服务重用,并且,一个新开发项目正期待它参与另一个服务组合。
对于下一个服务组合,项目组要求服务添加新的功能——生成非常详细的报表,包含各种记录明细及员工工作时长的统计信息和既往评估的排名。为了满足该需要,服务新增了一项功能,称之为 GetMasterReport。
该功能作为一个操作设计到 Web 服务契约当中,该操作接收参数化的输入数据,并输出一个大文档,包含各种统计信息及报表明细。
初步测试显示,对于某些该操作可接受的“从”(from)和“到”(to)的输入处理要用去数分钟,其原因是在产生所需的统一报表之前底层逻辑需要访问若干数据库并进行一系列计算。
于是这样的担心产生了,该功能可能会经常占用服务器时间以致于服务的整体可伸缩性的下降,进一步影响服务的可用性。最后,项目组决定将 GetMasterReport 操作的逻辑分开到一个专用服务器上。Employee Record 服务中设计了一个门面组件,用于为拆开的 MSTReportGenerator 组件传递请求及响应消息。
注:该案例分享没有图例,因为该图的服务架构看上去和 Invoice 服务示例中的图 16.28 基本相同。
查看英文原文: Patterns from “SOA Design Patterns” by Thomas Erl, Part 2 。
本章是图书 SOA 设计模式的一段书摘,该书 由 Thomas Erl 及其他供稿者合著,作为 Thomas Erl 关于面向服务计算丛书的一部分,于 2009 年 1 月由 Prentice Hall 出版,ISBN 0136135161,版权所有 2009 SOA System Inc.。更多信息请访问: informit.com/soa 或 soapatterns.org 。
感谢胡键对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论