服务设计系列的法则已经发展到最佳通信实践和取样相关编码的程度。本文提供了设计和实现网络服务的基本原理,并且对面向服务的体系结构(SOA)的相关概念做了一个简要的回顾,以及有关于几种模式和反模式的详细讨论,当构建网络服务时,开发者可以利用它们。它可应用于能进行网络服务开发和配置的任何编程语言或平台。

  •   关于SOA设计原理系列
  •   面向服务的体系结构(SOA)简介
  •   模式与反模式
  •   总结

  关于SOA设计原理系列

  这是一系列研究设计更有效的网络服务为目标的论文集的第一步。这第一篇论文是在微软的模式与实践小组的协助下完成的,以后的论文也可能会包含与其他组或个人的协作成果,不仅限于微软内部小组或个人。

  读者应该记住关于样本代码的警告,这些代码只是为了SOA设计原理系列开发的,它们仅用作举例说明目的。建议读者学习这些样本代码,而不应该试图将这其中的任何代码用于实际的开发环境。如果读者决定将样本代码用到实际开发环境中,微软公司不承担任何可能由此引发的损失。

  样本代码的系统配置要求:

  •   Microsoft Window XP(在其他平台上没有试过)
  •   Microsoft .Net Framework 1.1
  •   Microsoft Internet Information Server 6.0
  •   Microsoft SQL Server or Microsoft Access
  •   Microsoft Visual Studio 2003 不是必需的,但它会提供一个更好的全面的过程。

  如果发生不支持样本代码的情况,本文的作者期待你的反馈。

  注意:SOA不断地迅速发展,在这系列中的论文和样本代码也许会被修正,使其更加成熟。

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

  SOA已经变成一个众所周知的缩写词,如果你问其他两人关于SOA的定义,你极有可能会得到两个完全不同,甚至可能相互矛盾的答案。一些人将SOA描述成了一个业务上的IT行业基础或底层结构,其他人则希望SOA能提高IT行业的效率。在许多方面,SOA有点象Godfrey Saxe写的盲人和大象的故事,每个人都各有不同地描述大象,因为他们中的每个人都被他们的个人经历所左右(比如,摸大象鼻子的人相信大象是一条蛇,而摸大象长牙的人则认为大象是一杆矛),Saxe的大象太容易描述了,因为它就是一个存在的实体。然而,SOA是很难去描述的,因为设计理论无法作为一个实体显示出来。

  SOA是一种创建那些从自治服务中构建出的系统的途径,它使得整合变得可预见性而不是在事后进行计划,最终的解决方法很可能就是组合由不同语言开发的各种服务,这些服务以多种安全模式和业务流程运行在不同的平台。这个概念听起来是难以置信的复杂,但它已经不是新的了,一些人也许会认为,SOA是从那些基于已有技术的分布式系统的设计和开发中发展而来,有许多与SOA相关的概念,比如服务、发现以及后来的绑定,也与CORBA和DCOM密切相关。同样地,一些服务设计原理也很大程度上与早期的基于封装、抽象和接口的OOA/OOD技术有共同点。

  SOA也提示了一个很明显的问题——严格地来讲,服务是什么?简单来说,一个服务是一段程序,能通过定义完整的信息交换机制来进行互动,服务必须设计得可用而且稳定,当服务配置等为了改变而重新构建时,服务也要随之构建并持续下去。灵活性经常被视为SOA带来的最大的利益之一,举个例子,一个机构的业务流程如果在一个松耦合的底层结构上运行,那么它肯定比那些束缚于潜在的单一的应用软件的机构组织更开放更能适应变化,因为后者需要几周时间去改变以适应最小的变化。松耦合的系统导致了松耦合的业务流程,因为业务流程不再受制于潜在底层结构的限制。服务以及相关的接口必须保持稳定,而且使它们可以被重新设置、整合以满足业务上的不停的变化。服务通过标准的接口和定义明确的消息来维持稳定,换句话说,SOAP和XML schemas用作消息的定义。如果服务被设计成仅仅知道信息如何传递或得到,而且只执行简单的粒度较小的函数,那么它极有可能被一个更大SOA底层结构所重用。正如早前所提到的,回想OO设计原理中的封装和接口设计会有助于我们设计和构建重用的网络服务。通过进一步理解和掌握面向服务的四个原则,我们能将OO原理扩展到世界上所有的网络服务。

  原则1:明确清楚的分界线

  服务通过在明确定义的分界点上的消息传递而相互作用,跨区域层次的服务可能因为地理位置、信任因素或者其他执行要素而花费很大的代价。一个分界点就是在一个服务的公共接口和它内部的执行之间的边界,一个服务的分界点通过WSDL来发布的,而且也许包含了已有服务的期望声明。跨区域的服务因为下面几点原因而被认为是一项昂贵的任务:

  •   目标服务的物理位置是个不可知因素;
  •   安全以及信任模式一般在跨越每个分界点时会发生变化;
  •   在一个服务的公共和私有部分之间,数据的编组和广播需要对于其他资源的信任,对于服务本身来说,其中的某些资源是外部不可见的。
  •   当服务配置被构建以适应改变时,服务也被构建以持续下去。这意味着,由于网络的重配置或者迁往其他物理位置时,一个原本可靠的服务而突然发生改变甚至退化。
  •   用户一般都不知道内部流程实现的保密程度。一个用户只能有限地控制所指定服务的运行。

  面向服务集成模式告诉我们,广域服务受限于网络的潜伏因素,网络故障和分布式系统故障,但是一个本地服务不会出现这样的问题。大量的错误检测和改正方案必须写出以处理使用远程对象接口带来的问题。我们假定跨区域是花费很高的,但也必须处理一些本地方法的配置中出现的警告,这些本地方法的作用就是使得跨区域的可能减到最小。一个只有单一本地方法和对象的系统也许会获得更高的性能,但是无法重用已经定义的服务(这种技术可比喻成OOP中的拷贝和粘贴,它在服务的版本上也遇到一样的问题)。

  关于SO的第一个原则,还有几点应该记住的:

  •   清楚你的边界点。服务有一个协约来定义它所提供的公共接口,服务的所有交互都通过公共接口发生,这个接口包括了公有流程和公有数据形参,公共流程是进入服务的入口,而公有数据形参则代表了流程使用的信息。如果我们使用WSDL来表示一个简单的协约,那么<message>表示公共数据,而<portType>表示公共数据,而 则表示公有流程。《Data on the Outside vs. Data on the Inside》这篇文章更加详细地调研了这些论点。
  •   服务应该易用。当设计一个服务时,开发者必须保证其他开发者很容易地使用它。服务的接口(协约)也应该被设计得不用破坏本身便可进一步扩展。(这个主题将在此系列后面的论文中更详细地讲解。)
  •   避免RPC接口。在类似RPC的模型中,外部信息传递应该是最主要的,它隔绝了用户与服务的内部实现,使服务开发者去进一步发展他们的服务,尽量减少服务用户之间的冲突(通过使用公有消息而不是公有方法来进行封装)。
  •   保持服务接口区域小。一个服务提供的公共接口越多,这个服务也就越难被使用和维持,所以,应该提供很小的定义明确的接口在你的服务中,这些接口应该相对简单,只是接收定义明确的输入消息以及返回定义明确的输出消息,一旦这些接口被设计完成,它们应该是静态的,作为服务的内部实现的对外接口,这些接口满足服务所必须支持的恒定的设计需求。
  •   内部实现细节不应该泄漏到服务分界点外,否则极有可能导致服务与用户之间更紧密的耦合。服务的内部实现应该屏蔽,因为它包括了版本和服务更新的选择。这篇文章的反模式部分列举了此类问题的详细例子。

  原则2:服务是自治的

  服务是实体,它们独立地配置、更新和管理。开发者不应该对于服务边界之间的空间做出假设,因为这些空间会比服务边界本身变得还快。比如,服务边界应该是静态的,将减小版本的更新给用户带来的影响。服务的边界一般都是稳定的,而关于策略、地理位置或网络技术等服务配置选项则会经常地变化。

  服务可以通过URI动态地设定地址,这使得地理位置、配置技术的改变或者不断地发展变化对服务本身只有很小的影响(这就好比服务的通道)。这些变化对服务也许只能造成很小影响,但它们能会对基于这些服务的应用造成破坏性的作用。假如你当前正在使用的一个服务明天将被迁移到新西兰的一个网络中时,该怎么办?这种响应时间的变化也许会对用户造成不可预期或不可意料的影响。对于服务如何被使用,设计者们应该持一种非乐观的态度,比如服务失败、服务相关的行为(服务级)要面对变化等。异常处理和补偿措施的适当引入必须与任何一个服务点相关。除此之外,用户也需要去改变以适应所使用服务的最小响应时间,比如,用户需要有关于安全、性能、事务处理以及其他因素的不同级别的服务。一个灵活的策略可以使一个单一的服务支持SLAs(其他的策略也许关注于版本、定位或其他方面)。在不同级别服务上的通信性能期望都是保守估计,因为一个服务没必要知道其他服务的内部实现。

  不仅仅是用户需要以非乐观的态度来面对服务的运行,而且服务提供商也是如此。有时不用服务本身来通报,用户应该对服务失败这种情形有所预料。服务提供商也不能保证用户按照正常过程使用服务,比如,用户可能试图使用恶意的信息去通信或者违反正常服务交互的规则,服务的内部也必须试图去纠正这些不适当的使用,不管用户是什么意图。

  服务被设计成自治的,但也没有服务是孤立的。一个基于SOA的解决方案还未成型,它其中包括了许多为一个具体方案而配置的服务。可以想到的是,在面向服务的环境中还没有可以用作指导的权威著作——一个管弦乐队指挥者的观念是不完善的(也就进一步意味着“回滚”的概念也是有缺陷的——这也是在后面论文中的议题之一)。实现自治服务的关键在于隔绝和退耦,所设计的服务相互独立地进行配置,并且只能用协议约定的信息和规范来进行通信。

  正如其他的服务设计的原则方法,我们能从过去OO设计的经验中学到很多。Peter Herzum和Oliver Sims在业务构件制造上(Business Component Factories)的工作提供了一些有趣的关于自治构件性质的见识。当他们的大部分工作与基于构件的解决方案相吻合时,基本的设计原则也仍然适用于服务设计。

  考虑到这些因素,这里有一些简单的设计要求来保证与SOA的第二原则相吻合:在配置和使用这些服务的系统中,服务应该被相互独立地配置和更新。协约应该根据“一旦被发布即不能更改”的假定来设计,这使得开发者不得不在他们的计划设计中考虑灵活性。考虑服务所面临的最坏情况,并加以解决。从用户的角度来讲,在服务的可用性和性能方面应该所有考虑或计划,从服务提供商的角度,则应该考虑到对服务的(故意)不适当使用,以及有可能出现的服务失败等情况。

  原则3:服务共享模式和协约,不是类

  如先前提到的,服务的交互应该建立在服务的策略、模型和基于协约的行为上。服务的协约一般都由WSDL来定义,服务集合的协约能用BPEL来定义说明(也就是说,BPEL使用WSDL来定义集合中每个服务的协约)。

  在一个问题领域内,大部分开发者都定义类来代表各种实体(比如,顾客、订单、产品),类把数据(消息)和对数据的操作结合到一个单独的编程语言或具体平台构造中。服务则打破了这种模型结构以求最大的灵活性和互动性,使用基于XML schema的消息来进行服务通信的方式对于编程语言和平台是不明确的,它只能确保服务边界之间的互动性,Schema定义了消息的结构和内容,服务的协约则定义服务本身的行为模式。

  总之,一个服务的协约包含了下列元素:

  •   XML Schema定义的消息相互交换格式
  •   WSDL定义的消息交换模式
  •   WS-Policy定义的功能需求
  •   BPEL也能被用作业务流程级别的协约以集合多个服务

  用户将依靠服务的协约去调用服务并与之交互,考虑到这方面的可靠性,服务的协约必须能保持稳定。服务协约利用XML Schema(xsd : any)的扩展性质和SOAP过程模型(可选择报头),应该尽可能清楚地设计出来。

  第三原则的最大挑战就是它的性能。一旦服务的协约被公布,那么它将很难修改,因为要减小对现有用户的影响。位于内部与外部数据之间的临界线对于服务是否能成功配置和重用是十分关键的。公共数据(传递于服务之间的数据)应该基于统一的标准,以确保跨服务的访问,而私有数据(服务内的数据)被封装在服务内。在很多方面,服务像是一个运行电子商务组织的较小个体。就像一个组织必须将外部的订单转换成内部订单格式那样,一个服务也必须将协约规范的数据转换成其内部格式。我们在面向对象的数据封装方面的知识说明了一个相似的概念——服务的内部数据只能通过服务的协约来操作。Pat Helland在Data on the Outside vs. Data on the Inside””中阐述了几个关于公共和私有数据的论题。

  考虑到这些因素,这里有一些简单的设计要求来保证与SOA的第三原则相吻合:

  •   确保服务协约的稳定,以减小对用户的影响。在某种意义来讲,协约意味着公共数据表示(数据)、消息交换模式(WSDL)和可配置的性能和服务级别(策略)
  •   为了尽可能减小误解,协约必须定义清楚明确。除此之外,它也能通过XML 语法和SOAP过程模型的扩展性,来兼容以后新版本的服务。
  •   避免在公共和私有数据表示之间的模凌两可的状态。服务的内部数据格式应该对用户隐藏,而它的公共数据模式是不变的(更适宜基于一个统一的实际行业标准)。
  •   当对于服务协约的更改不可避免时,要及时更新服务。这样能减小现有用户执行所带来的破坏。

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

  这个原则经常被考虑得最少,而它又或许是关于实现灵活网络服务的最重要因素之一。只通过WSDL来通信服务互动的一些需求是不可能的,策略表达式可以从语义兼容(消息如何被传递或者传递给谁)中分离出结构兼容(什么被通信)。

  操作要求能在机器可读的策略表达式中清楚阐述,策略表达式包含了一套能共同操作的语义来管理服务的行为。WS_Policy规范定义了机器可读策略框架,它能表达服务级别的策略、并在执行时间找到它们执行,比如,一个国家安全服务也许需要一个策略去强化某个具体服务级别(举个例子,已满足指定标准的护照照片必须再被恐怖分子识别系统再次检查)。与服务相关的策略信息能被很多其他实施不同检查点的服务所重用,网络服务策略不需要增加一行其他的代码就能加强这些需求,这一节也说明了策略框架如何提供其他的有关于服务需求方面的信息,也列举了用于服务定义和执行的一些已公布的编程模型。

  一个策略的断言定义了一种行为,这种行为就是一个策略的必要条件。(在上面一节中这个断言就是恐怖分子识别系统的检查)。断言中有具体领域的语义,并最终在相互无影响的应用于各种行业的具体领域的详细说明中定义(建立WS-Policy框架概念)。

  策略驱动的服务依然在不停地发展,开发者也应该保证他们的策略断言在服务期望和服务语义兼容上尽可能的清晰。

  模式与反模式

  既然你已经对SOA概念有了初步的了解(包括SO设计原则),那现在就开始应用我们所学到的,这篇论文的下面部分介绍了两种反模式和三种模式,每种反模式和模式都是基于先前讨论的概念来设计的。

  为什么有模式和反模式?

  人们趋向于在模式中思考和交流。Christopher Alexander已经写了几本关于模式语言的书,他定义模式是从一个具体中来的一个抽象,并且在具体的上下文中保持重现。模式和模式语言用来描述实践、设计证明和获取供别人学习的经验,模式是一条途径去迅速理解应用这些模式的设计指导方针和各种上下文,而对于反模式,顾名思义,与模式相反。在模式提供指导和实践时的地方,反模式则说明了公共设计缺陷,并且作为一种从其他错误中学习的方法,这篇论文的剩下部分介绍的所有模式与反模式能指导我们开发更有效的网络服务。

  这篇论文中的反模式与模式都将使用下列格式:

  •   上下文:模式与反模式的简要背景描述。有了上下文,读者才能去应用一个模式,以及在具体证明反模式之前就能识别一个反模式的特征。
  •   问题:一个简单的声明。被用来构成与模式或反模式相关的对象。
  •   影响:在应用已有模式和识别反模式时,一些额外的其他的因素也必须考虑进来。
  •   解决方法:已有反模式或者应用相关模式的必需步骤的详细描述。
  •   征兆和结果:反模式中,这些因素能生成反模式;而在模式中,征兆和结果也许提到了其他因素和考虑点来应用相关模式。

  反模式中有如何证明相关设计缺陷的附加建议。

  代码样本

  •   每个样本代码都被打包成安装文件(MSI)并且有README文件来说明如何安装和配置样本代码。
  •   读者应该知道关于样本代码使用的警告。样本代码只用作说明目的,读者可以学习,但是不应试图将任何样本代码应用到实际应用环境中。微软公司将不承担任何可能由此造成的损失。
  •   影响:在应用已有模式和识别反模式时,一些额外的其他的因素也必须考虑进来。

  反模式#1:CRUD(Create、Read、Update、Delete)接口

  •   上下文:
  •   要求你为新的企业SOA项目设计网络服务
  •   问题:
  •   你如何使用.Net来设计SOA服务
  •   影响:
  •   你是一个VB开发者并且知道如何创建构件
  •   这是你的第一个SOA项目
  •   其他平台的应用也能使用你的服务
  •   解决方法:
  •   就像设计以前的构件接口一样,来设计你的服务接口
  •   创建一个服务,它能实现CRUD操作
  •   列表1中列举了样本代码的一部分

  列表1:简单的VB.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的行为、调用创建和移位等功能,而不是发送一个定义明确的用来指示采取某个行动的消息。这违反了第一原则(良好的边界定义)和第三原则(只被模式共享)。
    •   接口有可能交互频繁,因为用户也许需要去调用两三个方法来完成工作。
    •   用一个提交来创建,当操作成功或失败时,用户可能不知怎么办。设计服务时始终把用户的期望考虑在内——用户需要知道什么?
    •   CRUD操作在网路服务的分解上是一个错误级别。它也许在服务内或服务之间实现,但是不应该让用户直接接触。这个例子就是说服务允许内部的实现出现在服务的公共接口中。
    •   这个接口意味着正式的交互,比如列举(查看MoveNext和Current方法)
    •   抽象类型(比如Current方法返回的对象)导致一个很脆弱的协约,这是例子违反了第三原则(只被模式共享)。
    •   如果服务在不一致的状态下可以抛弃数据,那么服务是十分危险的。如果用户增加一个新的访问(或更新已经存在的访问)并且没有调用CommitChanges方法时,将会发生什么?正如先前提到的,服务提供商不能信任用户始终能做出正确的事情。

  以下列方式改变服务能避免上面列举的这些风险条目:

  •   用XML schema改变服务接口的通信。这个模式也包括了指定的服务行为(比如,New Contact Schema或者Change Contact Schema)。开发者应该优先考虑现有的行业标准而不是急于开发自己的模式,一个满足你需要的模式也许已经存在。
  •   在私有方法后隐藏对数据的操作,只有使用公共接口的模式才能访问到。
  •   确保用户收到一个包含请求状态信息的确认消息。

  反模式#2:Loosey Goosey

  •   上下文:
    •   你能构建一个基于服务的解决方案
    •   你的组织对服务重用很重视
  •   问题:
    •   如何使得服务接口有最大的灵活性和扩展性
  •   影响:
    •   你计划去提供在解决方案的各个层次上的结合点
    •   其他组需要访问你的数据库
  •   解决方法:
    •   将服务接口设计成Data Tiers(列表2中)
    •   重视后期附加来设计高扩展性的接口

  列表2. 简单的VB.NET Data Tier服务

<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

  •   征兆和结果
    •   事实上没有协约存在。用户不知道如何使用服务(比如,什么是有效的命令、编码规则等)。
    •   接口允许其所发生的错误。协约既不清楚也具有很高的安全风险,很容易受SQL攻击的影响。
    •   协约没有提供足够的信息让用户知道如何使用服务,如果为了让用户知道如何使用服务而让他必须看一些东西而不是服务的署名,那么应该要重新回顾一下服务的分解。
    •   期望用户能够熟悉数据库和表结构而不是使用网络服务,这将导致服务提供商和用户之间更紧密的耦合。
    •   由于依靠后期的附加绑定和同一服务内边界之间的编/解码,服务的运行性能将会很差。

  以下列方式改变服务能避免上面列举的这些风险条目:

  •   用定义明确XML schema改变服务接口的通信。这个模式不应该透露任何有关于潜在的数据仓库的信息,这个模式的语义也应该能提供上下文给用户,使得他们能理解服务的目的。(这个方法能提高服务的运行能力)
  •   数据库交互应该被封装在私有方法中,不让用户知道数据库和与之相关的表的细节。
  •   确保用户收到一个包含请求状态信息的确认消息。

  模式#1:文档处理器(Document Processor)

  这个模式的样本代码可以下载获得

  •   上下文:
    •   构建一个基于服务的解决方法
    •   与SO设计原则相吻合
  •   问题:
    •   如何创建一个简单的定义明确的协约,并且与SO设计原则相吻合?
  •   影响:
    •   服务的协约应该支持以文档为中心的考虑。
    •   协约应该明确定义服务的语义(避免Loosey Goosey反模式)。
    •   协约应该在服务本身中封装其实现以支持松耦合(避免CRUD接口反模式)。服务协约是是一个边界,它不应该泄漏任何有关内部实现的信息(记住第一原则)。
    •   服务必须遵守WS-I Basic Profile,使得它能被任何支持网络服务的平台或编程语言使用。
    •   服务应该作为一个完整单元的工作代表一个业务过程(避免一些假设,比如在CRUD接口反模式中)。还是那句话,服务提供商不能信任用户能始终做出正确的事情,如果意外发生了,服务也不能依靠用户来调用其他的服务去恢复或维修它。
  •   解决方法:
    •   定义并重用一个XML Schema来表示一个请求和响应消息,确保所有公共的互动使用这些模式。(列表3中有一个样本代码片断)
    •   直接从模式中产生对象以加快开发,这有时就是一种协约优先(Contact-First)的方法,你在开发实际的服务前定义服务的协约,此协约能被用来产生服务代码。协约优先能减小互操作中的障碍,因为它建立在很多技术的经验之上(CORBA、COM和DCE都使用接口语言)。网络服务有时采用协约优先的方法,因为SOAP经常不用WSDL来解决简单的问题。不管怎样,许多开发环境提供了对协约优先的简单支持,许多工具,比如WSCF和XSD Object Code Generator都能帮助进一步自动化此过程。
    •   服务提供商不能期望用户以具体的方式去调用和使用服务,这就是说,为一个已定的事务处理使用异常处理过程是合理的,但是服务提供商不能依靠用户去触发这个处理过程,一个保留模式(下面可见)为事务处理提供了最自治的保护,不需要用户来进行操作。

  列表3. Sample C# Document Processor Serivce

[WebMethod()]

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

  •   征兆和结果:

  定义使用简单模式的服务与SO设计原则相一致:

  •   将一个以文档为中心的网络服务映射到一个业务过程是比较容易的,因为用户倾向于将业务过程视为发送和接受文档的思维方式(注意,那些文档也许代表着业务事件和不真实的业务文档)。
  •   服务边界作为一条临界线来实现公共数据和私有数据之间的转换。
  •   服务的实现细节对用户应该是不可见的。
  •   采用协约优先的方法确保服务有很高的互操作性。
  •   以文档中心的服务更容易得到发展,因为所有的互操作都是通过消息而不是实际代码的RPC来实现的。

  这种实际做法中还有一些次要的东西需要注意:

  •   由于需要将数据内部到外部的转换,运行性能可能成为一个问题。
  •   用户必须将他们的数据表示转换成服务所使用的模式,一些用户也许能觉得映射到模式是一个很麻烦的过程。

  模式#2:多重复消息(Idempotent Message)

  这个模式的样本代码可以下载获得

  •   上下文:
    •   与SO设计原则相符合
    •   构建一个基于服务和事务上的解决方法
    •   如果发生了同一个消息的多次递交,服务必须能够给予修正。
  •   问题:
    •   如何确保消息是重复的?
  •   影响:
    •   不能期望用户太多。
    •   你工作在一个事务的数据库系统,并且它需要频繁可靠的更新。
    •   一个可信赖的消息平台也不一定能解决问题,因为用户总能发送同一请求的多个副本。
  •   解决方法:
    •   服务协约(模式)需要从用户得到消息,以被一组工作识别器标识(此后就是一个UOW ID),举例图1。
    •   协约不能让UOW ID始终是唯一的。
    •   UOW ID代表着一个单元的工作,并且只被执行一次,不管有多少条有着相同UOW ID值的消息。
    •   服务将使用UOW ID来决定一个单元的工作是否已经被执行,还是只是启动它。有很多选项来处理与UOW IDs相关的已经完成的工作:
      •   1. 返回缓存副本。
      •   2. 再次发送消息,就好像第一次请求没有接收到一样。
      •   3. 抛出异常和返回错误。

  图 1:多重复消息模式

  图 1:多重复消息模式

  •   征兆和结果:

  多重复消息是一个较难的论题,处理重复UOW IDs的三个选项中每一个都有几个考虑因素:

  •   返回缓存副本——这个选项需要服务在一定的时间内保留一个缓存响应,决定时间值或缓存刷新应该被相关的业务过程所决定。这里还有一些其他的因素要考虑:
    •   ?当前值不同于缓存中的值吗?
    •   ?如果响应是错误的,怎么办?
    •   ?如果用户重用UOW IDs去做不相关的工作,怎么办?
  •   再次发送消息——对于简单的读操作,这是无害的。但是对于写操作,这无疑是可怕的(比如,付清帐单)。如果第一次处理UOW ID就发生了错误了,怎么办?再处理一次可能导致相同的错误(这有时就是一个有害的消息循环)
  •   抛出异常——用户也许没有收到服务的第一次响应并且简单地再次重发了同一个UOW ID(可能由于一个响应超时了)。如果最后一次发送也丢失了,用户将如何接收响应呢?

  UOW ID应该作为响应模式的一部分,将重复请求的处理与相关业务过程联系起来,UOW ID也可被加入成一个自定义的SOAP报头,使得重复请求的处理成为整个消息处理基础的一部分,URI应该被包含以帮助探测重复的进入,一系列的修正措施也应该被自动保留,使修正跟踪能返回给UOW ID。最后,关于缓存刷新的论题,在响应被接收的时候反映响应时可被进一步讨论,缓存管理是一个另外的难题,并且已经超出了本论文的范围。

  支持多重复消息机制将提高服务的自治性(第一原则),因为服务不再依靠用户是否做正确的事情,还有一些方面需要注意的:

  •   使用模式的服务将肯定会使用大量的存储空间来容纳缓存中的响应。
  •   由于缓存的管理,势必会对服务的运行性能造成较大的影响。

  模式#3:预留(Reservation)

  这个模式的样本代码可以下载获得。

  •   上下文:
    •   与SO设计原则相符合。
    •   有一个复杂的业务过程想要提供给用户,这个业务过程在单个事务处理时需要几个小时。
  •   问题:
    •   在一个长时间运行的过程中,如何保持数据的一致性?
  •   影响:
    •   分布式的事务处理不能被共享。
    •   这样的业务过程需要几个消息才能完成。
    •   消息交换所需要的时间从几秒到几个小时。
  •   解决方法:
    •   ?消息创建试验型的操作——这些试验操作是原子级的事务处理,它能保证数据库在一致的状态。
    •   三个试验操作都有三种可能的输出结果:
      •   1. 试验操作被证实(通过消息交换的完成)
      •   2. 由于一次失误或其他参与者有意识地操作,试验操作被取消。
      •   3. 试验操作(消息交换)在期望的服务级别(时间范围)内没有完成。
    •   必须定义一个对话机制去追踪每个试验操作的状态,分配唯一的识别器(预留ID)和时间戳给每个对话可以使服务记住每个对话结束的位置。图2说明了这种模式。
    •   用户试图确认已注册的预留时,必须包括一个有效的预留ID,如果确认请求缺少或无效的预留ID,那么此请求将被服务拒绝。
    •   终止时间戳使服务在有效时间段后,终止不确定的预留。

  图 2:预留模式

  图2 预留模式

  •   征兆和结果:
    •   分配唯一的预留IDs和时间终止戳能确保服务和相关的业务规则处理数据一致性问题。
    •   预留IDs用来跟踪对话的状态。
    •   时间终止戳则以预定方式来处理超时和消息丢失。

  预留ID和时间终止戳应该是验证请求模式的一部分,将预留过程与实际业务过程联系起来。对于每个预留请求,服务为之产生预留IDs又生成时间终止戳,使服务能周期性地检查和终止不确定的预留,这种模式可以与多消息模式相结合,并进一步使服务与重复预留请求相隔离。

  还有一些需要注意的方面:

  •   处理预留请求的业务规则必须明确定义,预定过多将如何处理?
  •   我们有点习惯于两个阶段的提交模型,并且经常试图在不适合的地方应用它(比如,长时间运行过程)。长时间运行过程必须保持原子过程的一致性,在这种长时间运行过程中的工作隔离不是一个简单的任务或一个模式,比如预留就是提到此论题的简单尝试。

  总结

  SO的四个原则提供了一系列基本的规则,它们能引导服务发展的方向,这篇论文所列举的模式和反模式都用来说明这些原则如何对服务设计产生的影响,我们也提供一些额外的指导来确保你们将来的服务设计和发展方向将会取得成功:

  •   当代收服务时,将业务过程在已有文档和经确认的业务事件的基础上进行模型化。
  •   服务接口的灵活性时很重要的,但也要避免太过于灵活而导致服务协约变得模糊不清。
  •   不要期望用户始终做正确的事情,如果服务需要用户按照先前定义好的方式去执行一系列的步骤,那么需要寻找一些模式(比如,预留)去维护加强这些步骤。
  •   不能将服务和相关的资源处于不一致的状态。

  还有许多其他的设计理念与网络服务相关,这系列中以后的论文将会阐述有关于版本更新、服务因素和策略驱动的服务配置等方面。

posted on 2007-03-14 20:18  小姜  阅读(699)  评论(0编辑  收藏  举报