服务设计的原则:服务模式与反模式

转载 2006年05月22日 11:52:00

摘要:“服务设计的原则”系列文章旨在彼此交流这方面的最佳实践经验和示例代码。本文是系列文章中的第一篇,主要提供基本的 Web 服务设计和实现原则,简要介绍了“面向服务的体系结构”(SOA) 的概念,详细讨论了开发人员在构建 Web 服务时可以利用的几种模式和反模式。这些原则适用于任何可以开发和部署 Web 服务的编程语言或平台。

Microsoft Patterns & Practices 团队 Ron Jacobs 一篇网络广播的启发

本文中的许多观点和概念源自 Patterns and Practices 团队的 Ron Jacobs,特此感谢。2004 年底,Ron 和我基于这些内容撰写了一篇网络广播。如果没有 Ron 的帮助,本文是无法完成的。

要了解关于 Ron 在 Patterns and Practices 团队所做工作的更多信息,请参阅 http://www.microsoft.com/resources/practices/default.mspxhttp://www.gotdotnet.com/team/rojacobs/

下载本文相关资料:

服务设计的原则:以文档为中心的模式(英文)。

服务设计的原则:幂等模式(英文)。

服务设计的原则:预订模式(英文)。

*
本页内容
关于 SOA 设计原则系列文章 关于 SOA 设计原则系列文章
简介面向服务的体系结构 (SOA) 简介面向服务的体系结构 (SOA)
SOA 模式与反模式 SOA 模式与反模式
结论 结论

关于 SOA 设计原则系列文章

本文是系列文章中的第一篇,集中讨论如何设计更有效的 Web 服务。此开篇文章是与 Microsoft 的 patterns & practices 团队合作完成的,今后我们还会与 Microsoft 内部和外部的其他团队和个人合作。

读者应慎用“SOA 设计原则系列”开发的示例代码。示例代码仅用于演示目的,鼓励读者研究、编译和学习这些代码。读者应避免在生产环境中尝试使用任何示例代码。如果读者决定在生产环境中使用任何示例代码,MICROSOFT 对其造成的损害不承担任何责任。

示例代码的系统要求:

Microsoft Windows XP(示例未在其他平台上测试)

Microsoft .NET Framework 1.1

Microsoft Internet Information Server 6.0

Microsoft SQL Server 或 Microsoft Access

Microsoft Visual Studio 2003 不是必需的,但如果使用会使总体效果更佳

虽然 Microsoft 对于示例代码不提供支持,但本文的作者仍欢迎您给予反馈。

注意 由于 SOA 正在快速发展,随着它的不断成熟,本系列中的文章和示例代码可能会有修订。

返回页首返回页首

简介面向服务的体系结构 (SOA)

目前,SOA 已成为广为人知而且存在些分歧的缩写词。如果要两个人给 SOA 下个定义,可能会有两个差别悬殊、甚至可能是截然对立的答案。有人将 SOA 描述为启动业务的 IT 基础结构,而其他人则认为 SOA 可以提高 IT 的效率。在许多方面,SOA 有点像 John Godfrey Saxe 的那首盲人与象的诗:每个人对那头象都有不同的描述,因为他们都受到了各自经验的影响(例如,摸到象鼻子的人认为它是条蛇,而摸到象牙的人则认为它是矛)。相对而言,Saxe 先生的象还是很容易描述的,因为它毕竟是真实存在的实体:而 SOA 更加难以描述,因为设计思想是无法以物理形式表现的。

SOA 是用于从自治服务构建系统的体系结构方法。有了 SOA,集成更具前瞻性,而不是事后反思:最终的解决方案可能由以不同编程语言开发的服务组成,由具有各种安全模型和业务流程的不同平台托管。尽管这一概念听起来惊人的复杂,它却并不是新生事物:有人认为,SOA 是从基于先前可用技术设计和开发分布式系统的经验演化而来的。与 SOA 相关的许多概念,如服务、发现和后期绑定,都与 CORBA 和 DCOM 有关。同样,许多服务设计原则与早期基于封装、抽象和定义明确的接口的 OOA/OOD 技术有着许多共同之处。

缩写词 SOA 提出了一个很明显的问题:到底什么是服务?简而言之,服务即可以通过定义明确的消息交换进行交互的程序。服务的设计必须考虑可用性和稳定性。服务的构建要考量持久性,而服务配置和聚合的构建则要考量变化性。灵活性经常上升为 SOA 的一个最大优势:与受底层单一性应用程序的约束、最小的变动也要几个星期才能实现的组织相比,在松散耦合的基础结构上实现业务流程的组织对于变动要开放得多。松散耦合的系统会带来松散耦合的业务流程,因为业务流程不再受底层基础结构局限性的约束。服务及其关联接口必须保持稳定,以便能够重新配置或重新聚合来满足日新月异的业务需求。服务的稳定性依赖于基于标准的接口和定义明确的消息:即,用于消息定义的 SOAP 和 XML 架构。如果服务用于执行简单而精确的功能、几乎不知道消息是如何传递过来的或是如何检索出来的,那么它在更大的 SOA 基础结构中被重用的可能性更大。如前所述,在我们设计和构建可重复使用的 Web 服务时,应当回顾有关封装和接口设计的 OO 设计原则。通过进一步理解常说的面向服务的“四条原则”,我们可以将这些 OO 原则扩展到 Web 服务领域。

原则 1:边界是显式的

通过跨越定义明确的边界进行显式消息传递,服务得以彼此交互。有时候,跨越服务边界可能要耗费很大的成本,这要视地理、信任或执行因素而定。边界是指服务的公共接口与其内部专用实现之间的界线。服务的边界通过 WSDL 发布,可能包括说明特定服务之期望的声明。跨越边界之所以被认为是代价高昂的任务,是有多种原因的,下面列出其中几个:

目标服务的物理位置可能是未知的因素。

安全和信任模型可能会在每次跨越边界时发生改变。

在服务的公共表示和专用表示之间封送和转换数据可能需要依赖额外的资源:其中一些资源可能在服务之外。

服务的构建要考量持久性,而服务配置的构建则要考量变化性。这一事实暗示着:由于网络重新配置或者迁移到另一个物理位置,可靠的服务的性能可能会突然降低。

服务的使用者通常不知道专用的内部过程是如何实现的。特定服务的使用者对正使用的服务的性能只能进行有限的控制。

面向服务的集成(英文)模式告诉我们“服务调用可能会受到网络滞后、网络故障和分布式系统故障的影响,而本地实现则不会。要预先考虑使用远程对象接口的影响,就必须编写大量的错误检测和更正逻辑。”尽管我们认为跨越边界是代价高昂的过程,但在部署用于将此类边界跨越减至最少的本地方法时,我们还是要格外谨慎。实现单一性本地方法和对象的系统可能会获得性能的改善,但功能性却与先前定义的服务完全一样(此项技术在 OOP 中称为“剪切与粘贴”,同样存在服务版本更新的风险)。

关于第一条 SO 原则,有几点原则需要铭记在心:

弄清边界。服务提供一个合约来定义其提供的公共接口。与服务的所有交互都通过公共接口进行。接口由公共进程和公共数据表示组成。公共进程是通向服务内部的入口点,而公共数据表示则是指该进程使用的消息。如果我们使用 WSDL 代表一个简单的合约,则 <message> 代表公共数据,而 <portType> 代表公共进程。文章“外部数据与内部数据”(英文)更详细地研究了这些问题。

服务应易于使用。设计服务时,开发人员应使其易于其他开发人员使用。设计的服务接口(合约)也应允许服务在不中断与现有使用者之间的合约的情况下进一步发展。(这一主题将在本系列的后续文章中更详细地讨论。)

避免使用 RPC 接口。应采用显式消息传递,而避免使用 RPC 之类的模型。这种方法将使用者与服务实现的内部隔离开来,使开发人员可以集中精力改进他们的服务,同时将对服务使用者的影响降至最低(使用公共消息而不是公用的方法进行封装)。

尽量减小服务的表面积。服务的公共接口越多,就越难以使用和维护。应当少提供服务的定义明确的公共接口。这些接口应该相对简单,主要用于接受定义明确的输入消息并以同样定义明确的输出消息进行响应。这些接口一旦定义,即应保持不变。这些接口提供服务必须支持的“恒定不变”的设计要求,为服务专用的内部实现充当门面。

内部(专用)实现的细节不应泄露到服务边界之外。如果将实现细节泄露到服务边界,很可能会使服务与服务使用者之间的耦合更加紧密。服务使用者不应当获知服务实现的内部情况,因为这样会使服务的版本更新或升级受到限制。本文的“反模式”部分就此问题提供了一个详细的示例。

原则 2:服务具有自治性

服务是独立进行部署、版本控制和管理的实体。开发人员应避免对服务边界之间的空间进行假设,因为此空间比边界本身更容易改变。例如,服务边界应当是不变的,只有这样才能将版本更新对使用者的影响降至最低。虽然服务边界是相当稳定的,但策略、物理位置或网络拓扑等服务部署选项却很可能发生改变。

服务可以通过 URI 动态寻址,使其底层位置和部署拓扑可以在几乎不影响服务本身的情况下改变或演化(服务的通信通道也是如此)。尽管这些更改对服务没什么影响,但它们对使用服务的应用程序却有着破坏性的影响。如果您今天使用的服务明天被移动到新西兰,将会怎样呢?响应时间的改变可能会对服务的使用者造成计划之外或意料之外的影响。服务设计者对于服务的使用方式应当采取谨慎的态度:服务将失败,其相关的行为(服务级别)可能会被更改。适当级别的例外处理和补偿逻辑必须与任何服务调用相关联。此外,服务使用者可能需要修改其策略,以声明要使用的最短服务响应时间。例如,服务使用者可以对安全、性能、事务及许多其他因素请求不同的服务级别。可配置的策略允许单个服务支持多个有关服务调用的 SLA(而其他策略可能主要关注版本更新、本地化及其他问题)。服务级的通信性能期望始终是自治,因为服务彼此之间不需要熟悉对方的内部实现。

不止是服务使用者应该对性能采取谨慎的态度:服务提供者在预测其服务的使用方式时也应同样谨慎。应该预料到服务使用者有时候会失败,却又不通知服务本身。服务提供者也不能信任使用者总是“为所应为”。例如,使用者可能会尝试使用不正常的/恶意的消息进行通信,或者尝试违反成功实现服务交互所必需的其他策略。无论用户意图如何,服务内部都必须尝试对此类不恰当的使用进行补偿。

虽然服务是自治的,但任何服务都不是孤岛。基于 SOA 的解决方案是不规则的,由许多为特定解决方案配置的服务组成。从自治的角度思考,您很快就会发现面向服务的环境中并没有主领机构:编排服务中的“指挥器”概念是错误的(进一步意味着,跨服务的“回滚”概念也是错误的:但这最好留待在另一篇文章中讨论)。实现自治服务的关键是隔离和去耦合。服务都彼此独立地设计和部署,并且只能使用合约驱动的消息和策略进行通信。

至于其他服务设计原则,我们可以学习过去 OO 设计的经验。Peter Herzum 及 Oliver Sims 就 Business Component Factories 的著作提供了对自治组件特性的一些有趣见解。虽然他们大部分的工作最适于大粒度、基于组件的解决方案,但其基本设计原则仍然适用于服务设计。

鉴于这些考量,在此提供一些有助于确保符合第二条 SO 原则的简单设计原则:

服务的部署和版本控制应独立于部署和使用它们的系统。

合约的设计应符合以下假设,即一旦公布即不可修改。这种方法迫使开发人员在其架构设计中构建灵活性。

采取谨慎的态度,使服务免于故障。从使用者的角度,规划服务可用性和性能的不可靠级别。从提供者的角度,预计服务会被误用(故意或其他方式),预计服务使用者会出现故障:而服务可能得不到通知。

原则 3:服务共享架构和合约,但不共享类

如上所述,服务交互应当只以服务的策略、架构和基于合约的行为为基础。服务的合约通常使用 WSDL 定义,而服务聚合的合约则可以使用 BPEL 定义(进而,对聚合的每个服务使用 WSDL)。

大多数开发人员定义类来代表特定问题空间中的各种实体(例如,“客户”、“订单”和“产品”)。类将行为与数据(消息)组合到一个特定于编程语言或平台的构造函数。服务打破了这种模式,最大程度提高了灵活性和互操作性。使用基于 XML 架构的消息进行通信的服务独立于编程语言与平台,从而拓宽了互操作性的级别。架构用于定义消息的结构和内容,而服务合约则用于定义服务本身的行为。

总起来说,服务的合约由以下元素组成:

使用“XML 架构”定义的消息交换格式。

使用 WSDL 定义的“消息交换模式”(MEP)。

使用 WS-Policy 定义的功能和要求。

BPEL 可以用作业务流程级合约,用以聚合多个服务。

服务使用者将依靠服务的合约来调用服务及与服务交互。鉴于这种依赖性,服务合约必须长期保持稳定。在利用 XML 架构 (xsd:any) 和 SOAP 处理模型(可选标头)的可扩展性的同时,合约的设计应尽可能明确。

“第三条原则”的最大挑战是其持久性。服务合约一旦公布,要想修改它而又尽可能减小对现有服务使用者的影响,是极其困难的。内部数据表示与外部数据表示之间的界线对于特定服务的成功部署和重用至关重要。公共数据(在服务之间传递的数据)应基于组织或纵向标准,确保能够跨不同服务被广泛接受。私有数据(服务内部的数据)封装在服务之内。在某种程度上,服务就像是实施电子商务交易的组织的缩微代表。如同组织必须将外来采购订单映射为其内部 PO 格式一样,服务也必须将通过合约达成一致的数据表示映射为其内部格式。与前面一样,我们可以再次使用 OO 数据封装经验来阐明类似的概念:服务的内部数据表示只能通过服务合约处理。Pat Helland 在“外部数据与内部数据”(英文)中探讨了几个关于公共数据表示与私有数据表示的问题。

鉴于这些考量,在此提供一些有助于确保符合第三条 SO 原则的简单设计原则:

确保服务合约保持稳定,以将对服务使用者的影响降至最低。这里的合约指公共数据表示(数据)、消息交换模式 (WSDL) 和可配置的功能和服务级别(策略)。

合约的设计应尽可能明确,以将误解减至最少。此外,应通过 XML 语法和 SOAP 处理模型的可扩展性使合约能够适应未来服务的版本更新。

避免使公共数据表示与私有数据表示之间的界线混淆不清。使用者不应看到服务的内部数据格式,而其公共数据架构应不可改变(最好基于组织标准、事实标准或行业标准)。

当不可避免要更改服务合约时,要对服务进行版本控制。这种方法对现有使用者实现的破坏程度最小。

原则 4:服务兼容性基于策略

尽管它往往被认为是最不为人所了解的原则,但对于实现灵活的 Web 服务,它或许是最有力的。单纯依靠 WSDL 无法交流某些业务交互要求。可以使用策略表达式将结构兼容性(交流的内容)与语义兼容性(如何交流消息或者将消息交流给谁)分隔开来。

服务提供者的操作要求可以通过计算机能识别的策略表达式来表现。策略表达式提供一组可以配置的可互操作语义,用以控制特定服务的行为和期望。WS-Policy 规范定义了一个计算机能识别的策略框架,它可以表达服务级策略,使它们得以在运行时被发现或实施。例如,政府安全服务可能需要一个实施特定服务级别的策略(例如,必须对照恐怖分子识别系统对符合指定条件的护照像片进行交叉检查)。与此项服务关联的策略信息可以用于许多与实施背景检查有关的其他情形或服务。WS-Policy 无需任何额外代码即可用于实现这些要求。本例阐明策略框架如何在提供业务定义和执行的声明编程模型的同时,提供有关服务要求的附加信息。

策略声明可以确定哪些行为是策略主体的要求(或功能)。(在上面的例子中,声明是指对照恐怖分子识别系统进行背景检查。)声明提供特定于域的语义,并最终在各纵向行业的单独、特定于域的规范之内定义(建立 WS-Policy “框架”概念)。

尽管策略驱动的服务仍然在不断发展,但开发人员仍应确保其策略声明在服务期望和服务语义兼容性方面尽可能明确。

返回页首返回页首

SOA 模式与反模式

既然您已经对 SOA 概念(包括 SO 设计原则)有所了解,就让我们开始将学习到的知识付诸实践吧。本文余下篇幅将介绍两个“反模式”和三个“模式”。这些“反模式”和“模式”都建立在前述概念的基础之上。

为什么使用模式和反模式?

人们的思考和交流往往遵循一些模式。几部关于模式语言的书籍的作者 Christopher Alexander 将模式定义为“由在特定的非任意上下文中不断出现的具体形式概括出来的抽象概念”。可通过模式和模式语言描述最佳实践方法、已被认可的设计并记录过去经验,在某种程度上供他人从中学习。模式是快速理解设计原则和它们所应用的各种环境的有效方法。而如您所料,反模式与模式相对。模式提供实践证明的指导原则和最佳经验,而反模式则阐明常见的设计缺陷,用于吸取他人的教训。本文余下的篇幅简略介绍两个反模式和三个模式,它们都可以帮助开发更有效的 Web 服务。

本文中的反模式和模式遵循下列格式:

上下文:模式或反模式的简要背景。提供上下文是为了帮助读者在彻底实例化之前发现可能应用模式或识别出反模式特征的机会。

问题:简单的描述,旨在制定与模式或反模式相关的目标。

影响因素:应用特定模式或识别反模式时必须考虑的其他事项。

解决方案:对特定反模式的解决方案或应用相关模式的必要步骤的描述。

故障现象与后果:对于反模式,是指导致反模式存在的因素。对于模式,故障现象与后果可以描述在应用相关模式之前要考虑的其他因素和事项。

反模式中还包括关于如何改进相关设计缺陷的其他建议。

关于代码示例的说明

每个模式的代码示例都可以下载。

每个示例均已包装为安装文件 (MSI),并且提供说明示例的安装和配置方法的 README 文件。

如上所述,读者应慎用示例代码。示例代码仅用于演示目的,尽管我们鼓励读者研究、编译和学习这些代码,但读者应避免在生产环境中尝试使用任何示例代码。如果读者决定在生产环境中使用任何示例代码,MICROSOFT 对其造成的损害不承担任何责任。

反模式 1:CRUDy 接口

上下文:

您需要为新公司 SOA 项目设计 Web 服务。

问题:

如何使用 .NET 设计 SOA 服务?

影响因素:

自 Visual Basic 5 后,您一直是 Visual Basic 开发人员,“知道”如何创建组件。

这是您的第一个 SOA 项目。

您所在的组织希望其他平台应用程序使用您的服务。

解决方案:

就像过去设计组件接口那样设计服务接口。

创建实现 CRUD(创建、读取、更新、删除)操作的服务。

示例代码段如代码清单 1 所示。

代码清单 1. Visual Basic .NET CRUD 服务示例

<WebMethod()> _
Public Sub Create( ByVal CompanyName As String, ByVal 
ContactName As String, ByVal ContactTitle As String, 
ByVal Address As String, ByVal City As String, ByVal 
State As String, ByVal Zip As String, ByVal Country As 
String, ByVal Telephone As String, ByVal Fax As String) 

<WebMethod()> _
Public Function MoveNext() As Boolean
End Function

<WebMethod()> _
Public Function Current() As Object
End Function

<WebMethod()> _
Public Sub UpdateContactName( ByVal NewName as String)
End Sub

<WebMethod()> _
Public Function CommitChanges()
End Function

故障现象与后果:

此接口设计鼓励 RPC 形式的行为,即调用 Create、MoveNext 等等,而不是发送用于指示要采取哪些操作的定义明确的消息。这违反了第一条(定义明确的边界)和第三条(只共享架构)原则。

接口的通信很可能过度频繁而琐碎,因为使用者可能需要调用两三个方法才能完成其工作。

Create 方法使用 Sub 意味着使用者无从知道操作是成功还是失败。设计服务时,始终要牢记使用者的期望:使用者需要知道什么?

CRUD 操作对于 Web 服务是错误的分解级别。CRUD 操作可以在服务内部或不同服务之间实现,但是不应以这种方式提供给使用者。有些服务允许内部(私有)功能提供给服务公共接口,这就是一例。

此接口意味着将发生有状态的交互,如枚举(参见 MoveNextCurrent 函数)。

抽象类型(如 Current 函数返回的 Object)将导致弱合约。这是违反第三条原则(只共享架构)的又一例。

这是一个非常危险的服务,因为它可能会使底层数据处于不一致的状态。如果使用者添加新的“联系人”(或者更新现有“联系人”),而从不调用 CommitChanges 函数,那样会发生什么情况呢?如前所述,服务提供者不能信任使用者总是“为所应为”。

按以下列方式更改服务后,可以避免上列风险和问题:

将服务接口改为使用 XML 架构进行通信。架构还可以包括目标服务操作(例如,“新建联系人架构”或“更改联系人架构”)。开发自己的架构之前,开发人员应参考现有行业标准。满足您需要的架构可能已经存在。

将数据处理封装在私有方法之内,只能使用传递给公共接口的架构访问。

确保服务使用者收到包含请求状态的确认。

反模式 2:Loosey Goosey

上下文:

要构建一个基于服务的解决方案。

您的组织对服务的重用很重视。

问题:

如何使服务接口有最大的灵活性和可扩展性?

影响因素:

您计划在解决方案的所有级别提供集成点。

其他组需要访问您的数据库。

解决方案:

将您的服务接口设计为“数据级”(参见“代码清单 2”)。

重视后期绑定来设计高扩展性的接口。

代码清单 2. Visual Basic .NET 数据级服务示例

<WebMethod()> _
Public Function QueryDatabase( ByVal Database as String, 
SQLQuery as string) As DataSet

<WebMethod()> _
Public Function Execute( ByVal Command as Integer, 
Arguments as string) As Boolean

故障现象与后果:

实际上没有合约存在。服务使用者不知道如何使用服务(例如,什么是有效的“Command”参数和编码期望等等)。

接口接受消息时太过自由。合约既不清晰,又存在严重的安全风险,容易受到 SQL 插入代码攻击。

合约没有提供足够的信息让使用者知道如何使用服务。如果使用者必须读取服务签名之外的一些信息才能了解如何使用服务,那么应检查服务的分解。

使用者需要在使用 Web 服务之前熟悉数据库和表格结构。这将导致服务提供者和使用者之间发生紧耦合。

由于依赖后期绑定和在同一服务内边界之间的编码/解码,服务的运行性能大大降低。

按以下列方式更改服务后,可以避免上列风险和问题:

将服务合约改为使用定义明确的 XML 架构进行通信。架构不应提供有关底层数据仓库的任何信息。架构的语义应该为服务使用者提供上下文,使它们能够了解服务的目的。(这一方法还可以提高服务的性能。)

数据库交互应封装在私有方法之内,使使用者与数据库及其关联表格的详细信息隔离。

确保服务使用者收到包含请求状态的确认。

模式 1:文档处理器

此模式的示例代码可以下载。

上下文:

要构建一个基于服务的解决方案。

应符合 SO 设计原则。

问题:

如何创建简单易用、定义明确而又符合 SO 设计原则的合约?

影响因素:

服务的合约应鼓励以文档为中心的设计思路。

合约应清晰地定义服务语义(避免 Loosey Goosey 反模式)。

合约应将实现封装在服务之内或之后来促进松散耦合(避免“CRUDy 接口”反模式)。服务合约是不得泄露有关内部实现的详细信息的边界(回忆第一条设计原则)。

服务必须符合 WS-I Basic Profile(英文),以允许支持 Web 服务的任何平台或编程语言使用。

服务应作为一个完整的工作单元来表示业务流程(避免类似“CRUDy 接口”反模式中的有状态假设)。如前所述,服务提供者不能信任服务使用者总是“为所应为”。服务不应依靠调用另一服务的用户在发生异常时“回滚”或“修复”状况。

解决方案:

定义或重用 XML 架构以表示服务的请求和响应消息。确保与服务之间的所有公共交互都使用这些架构。(有关示例代码段,请参阅“代码清单 3”。)

直接从架构生成对象,以加速开发。有时候,这称为“合约至上”方法。利用“合约至上”的方法,应在开发实际服务之前定义服务合约。然后可以使用服务合约生成服务代码。“合约至上”对于减少互操作性阻碍是有效的方法,因为它建立在数十年经验的基础之上(CORBA、COM 和 DCE 都使用接口语言)。Web 服务有时采取“合约末位”方法,因为简单的解决方案通常使用 SOAP 而不是 WSDL。无论如何,许多开发环境提供对“合约至上”的简单支持,而 WSCF(英文)和 XSD Object Code Generator(XSD 对象代码生成器)(英文)之类的工具可用于帮助进一步自动化此过程。

如前所述,服务提供者不能期望使用者以特定方式调用或使用其服务。这意味着对给定事务使用补偿过程是可行的,但是服务提供者不应依靠服务使用者触发补偿。预订模式(见下)为事务提供最自治的保护,消除了对服务使用者的依赖。

代码清单 3. C# 文档处理器服务示例

[WebMethod()]

public FindCustomerByCountryResponse FindCustomersByCountry(
FindCustomerByCountry request) 
{
   ...
}

故障现象与后果:

使用此简单模式定义的服务将符合 SO 设计原则:

以文档为中心的 Web 服务更容易映射到业务流程,因为使用者往往将业务流程看成发送和接收文档(注意,文档可能代表业务事件,而不是实际的业务文档)。

服务边界相当于在公共数据表示与私有数据表示之间进行转换时的清晰界线。

服务的实现细节被封装起来,不让使用者知悉。

采取“合约至上”方法有助于确保服务的高度可互操作性。

以文档为中心的服务易于改进,因为所有交互都通过消息发生,而不是通过硬编码的 RPC 方法。

对于此方法,有一些小问题应该注意:

对于从内部表示到外部表示的数据传输,性能可能会成为一个问题。

服务使用者必须将其数据表示映射到服务使用的架构。某些使用者可能会发现映射到服务架构是一个得不偿失的过程。

模式 2:幂等消息

此模式的示例代码可以下载。

上下文:

应符合 SO 设计原则。

要构建一个基于服务的事务性解决方案。

如果要多次传送同一条消息,必须能够补偿服务(例如,消息应当是幂等的)。

问题:

如何能够确保消息是幂等的?

影响因素:

除了定义服务的合约,不能期望从使用者获得更多信息。

要实现需要频繁、可靠更新的事务性数据库系统。

可靠的消息传递平台可能并不能完全解决问题,因为使用者可能仍会发送同一请求的多个副本。

解决方案:

服务合约(架构)应要求使用者的消息带有“工作单元”标识符(此后称为 UOW ID)。参见“图 1”示例。

合约不能要求随着时间的变化 UOW ID 始终唯一。

无论具有同一 UOW ID 值的消息数量有多少,UOW ID 始终代表仅执行一次的工作单元。

服务将使用 UOW ID 确定工作单元在启动之前是否已经执行过。处理与已经完成的工作关联的 UOW ID 时,有三个可能的选项:

返回响应的缓存副本。

重新处理消息,就好像从未收到第一个请求一样。

引发异常,返回错误消息。

a

1. 幂等消息模式

故障现象与后果:

幂等消息传送是一个棘手的问题。处理重复 UOW ID 有三个选项,对其中的每个选项都有若干要考虑的地方:

1.

返回缓存的响应:此选项要求服务将可能的响应缓存维护一段给定时间。超时值/缓存刷新的确定应该由关联业务流程执行。其他还必须考虑的问题:

如果当前值与缓存值不同怎么办?

如果响应错误怎么办?

如果使用者重复使用无关工作单元的 UOW ID 怎么办?

2.

再次处理消息:虽然这对简单的读操作可能无害,但对写操作(例如,发票付款)则可能有害。如果在第一次收到 UOW ID 时对它的处理导致错误将会怎么样??再次处理似乎将导致同样的错误(这种情况有时称为“有毒消息循环”)。

3.

引发异常:这种情况下,服务使用者可能未收到服务的原始响应,而只是再次重新发送同一 UOW ID(可能因为预期响应超时)。如果最后一个发送的消息丢失,使用者将如何接收您的响应?

UOW ID 应该是请求架构的组成部分,将重复请求绑定到相关业务流程中。UOW ID 也可能是作为自定义 SOAP 标头添加的,从而使重复处理成为总体消息处理基础结构的组成部分。使用者的 URI 也应包括在内,以帮助检测重入。可以自动维护修改的审核追踪,使修改请求跟踪能够返回给定的 UOW ID。最后,与缓存“刷新”关联的问题可以通过反映收到请求时的响应来解决。缓存管理是超出本文范围的又一个难题。(建议有兴趣详细了解缓存问题的读者查看 MSDN 文章并发处理:设计服务与其代理之间的交互(英文)。

由于服务不再相信使用者能“为所应为”,故支持幂等消息传送提高了服务自治性(第一条设计原则)。对于此方法,有一些小问题应该注意:

使用此模式的服务将有可能使用大容量持久存储,以满足响应的缓存需求。

由于管理缓存,可能会对服务性能造成严重影响。

模式 3:预订

此模式的示例代码可以下载。

上下文:

应符合 SO 设计原则。

您有一个打算通过 Web 服务呈现给使用者的复杂的业务流程。该业务流程运行时间长,一个事务就要持续几个小时。

问题:

如何在长时间运行的流程中保持数据一致性?

影响因素:

无法共享分布式事务。

上述业务流程需要若干消息才能完成。

消息交换所需时间从几秒钟到几个小时不等。

解决方案:

消息引起试探性操作:这些试探性操作为原子事务,使数据库保持一致状态。

可能的结果有三种,它们与这些试探性操作中的每个操作都相关:

试探性操作(通过完成消息交换)被确认。

试探性操作被取消,或者是因为产生了故障(隐式),或者是被一个参与者取消(显式)。

试探性操作(消息交换)未在期望的服务级别内完成(超时)。

要跟踪每个试探性操作的状态,必须定义一个对话框。如果为每个对话框指定一个唯一标识符(“预订 ID”)和过期时间戳,可使服务“记住”每个对话框停止的位置。图 2 显示此模式的说明。

如果服务使用者试图确认先前注册的预订,则该使用者必须提供有效的“预订 ID”。缺少“预订 ID”或者“预订 ID”无效的确认请求将被服务拒绝。

过期时间戳使服务可以在超过给定时间段之后,使未确认的预订“过期”。

a

2. 预订模式

故障现象与后果:

指定唯一的“预订 ID”和过期时间戳可确保数据一致性问题由服务及其关联业务规则处理。如前所述,服务不能信任使用者“为所应为”。

“预订 ID”用于跟踪给定对话框的状态。

过期时间戳用于以可预测方式处理超时或者丢失的消息。

“预订 ID”和过期时间戳应该是“确认请求”架构的一部分,将预订处理绑定到实际业务过程中。服务为每个预订请求生成“预订 ID”和过期时间戳,使服务能够定期检查未确认的预订并使其“过期”。此模式可以与“幂等消息”模式组合,进而将服务与重复的预订请求隔离。

对于此方法,有一些小问题应该注意:

处理预订请求的业务规则必须定义清晰。将如何处理“预订超量”?

我们有点被原子两段提交模型扰乱了,常常试图将其应用于不很适合的场合(例如,运行时间长的过程)。运行时间长的过程必须保持组成它们的原子过程的一致性。运行时间长的过程中的工作隔离不是件简单的事情,像“预订”这样的模式是解决此问题的简单尝试。

返回页首返回页首

结论

四条“面向服务”原则提供了一组分立的基本原则,可用于指导服务开发工作。本文提供了几个模式和反模式,旨在说明这些原则如何影响服务设计。我们还提供了一些其他准则,用于帮助确保您将来的服务设计和开发工作获得成功:

分解服务时,根据现有文档和已知业务事件为业务过程建模。

虽然服务接口灵活很重要,但要避免太过灵活,以防底层服务合约变得不清晰。

不要期望服务使用者“为所应为”。如果服务要求使用者以预先定义好的方式执行一系列步骤,请借助于模式(例如,预订)帮助强制执行这些步骤。

永远不要使服务或其关联资源处于不一致的状态。

还有许多其他设计问题与 Web 服务关联。本系列的后续文章将讨论诸如版本控制、服务分解和策略驱动的服务配置等问题。

相关文章推荐

微服务架构的设计模式与使用到的基础框架

前不久,Java Code Geeks发表了一篇文章,分析单体应用与微服务的优缺点。近日,该网站又发表了一篇文章,提供了六种微服务架构的设计模式。 聚合器微服务设计模式 这是一种最常用也最简单的设...
  • he90227
  • he90227
  • 2016年04月14日 19:50
  • 7460

服务设计模式

  • 2016年03月20日 21:20
  • 55.72MB
  • 下载

Android歌词秀设计思路(6)运用Proxy设计模式简化歌词播放服务的使用

开始开发歌词秀的时候还是夏天,没有想到写这篇文章的时候大连已经迎来的今年的第一次大规模降温。多少有点冬天的感觉了。 上一篇文章我们已经介绍了,带有歌词播放功能的服务,按说接下来就该是利用歌词播放服务...
  • md521
  • md521
  • 2011年10月10日 12:53
  • 409

SOA原则:服务设计

  • 2012年08月19日 09:26
  • 27.59MB
  • 下载

面向海量服务的设计原则和策略总结

互联网服务的特点就是面向海量级的用户,面向海量级的用户如何提供稳定的服务呢?这里,对这几年的一些经验积累和平时接触的一些理念做一个总结。       一、原则       1.Web服务的CAP原...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:服务设计的原则:服务模式与反模式
举报原因:
原因补充:

(最多只允许输入30个字)