服务设计原理:服务模式和反模式
2007-02-23 12:20:49 来源:WEB开发网服务设计系列的法则已经发展到最佳通信实践和取样相关编码的程度。作为一系列相关论文的开始,本文提供了设计和实现网络服务的基本原理,并且对面向服务的体系结构(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
征兆和结果:
|
以下列方式改变服务能避免上面列举的这些风险条目:
• | 用XML schema改变服务接口的通信。这个模式也包括了指定的服务行为(比如,New Contact Schema或者Change Contact Schema)。开发者应该优先考虑现有的行业标准而不是急于开发自己的模式,一个满足你需要的模式也许已经存在。 |
• | 在私有方法后隐藏对数据的操作,只有使用公共接口的模式才能访问到。 |
• | 确保用户收到一个包含请求状态信息的确认消息。 |
反模式#2:Loosey Goosey
• | 上下文:
| ||||
• | 问题:
| ||||
• | 影响:
| ||||
• | 解决方法:
|
列表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
• | 征兆和结果
|
以下列方式改变服务能避免上面列举的这些风险条目:
• | 用定义明确XML schema改变服务接口的通信。这个模式不应该透露任何有关于潜在的数据仓库的信息,这个模式的语义也应该能提供上下文给用户,使得他们能理解服务的目的。(这个方法能提高服务的运行能力) |
• | 数据库交互应该被封装在私有方法中,不让用户知道数据库和与之相关的表的细节。 |
• | 确保用户收到一个包含请求状态信息的确认消息。 |
模式#1:文档处理器(Document Processor)
这个模式的样本代码可以下载获得
• | 上下文:
| ||||||||||
• | 问题:
| ||||||||||
• | 影响:
| ||||||||||
• | 解决方法:
|
列表3. Sample C# Document Processor Serivce
[WebMethod()]
public FindCustomerByCountryResponse FindCustomersByCountry(
FindCustomerByCountry request)
{
...
}
• | 征兆和结果: 定义使用简单模式的服务与SO设计原则相一致:
|
这种实际做法中还有一些次要的东西需要注意:
• | 由于需要将数据内部到外部的转换,运行性能可能成为一个问题。 |
• | 用户必须将他们的数据表示转换成服务所使用的模式,一些用户也许能觉得映射到模式是一个很麻烦的过程。 |
模式#2:多重复消息(Idempotent Message)
这个模式的样本代码可以下载获得
• | 上下文:
| ||||||||
• | 问题:
| ||||||||
• | 影响:
| ||||||||
• | 解决方法:
|
图 1:多重复消息模式
• | 征兆和结果: 多重复消息是一个较难的论题,处理重复UOW IDs的三个选项中每一个都有几个考虑因素:
|
UOW ID应该作为响应模式的一部分,将重复请求的处理与相关业务过程联系起来,UOW ID也可被加入成一个自定义的SOAP报头,使得重复请求的处理成为整个消息处理基础的一部分,URI应该被包含以帮助探测重复的进入,一系列的修正措施也应该被自动保留,使修正跟踪能返回给UOW ID。最后,关于缓存刷新的论题,在响应被接收的时候反映响应时可被进一步讨论,缓存管理是一个另外的难题,并且已经超出了本论文的范围。(对于缓存方面有浓厚兴趣的读者可以参见MSDN 文章Dealing with Concurrency: Designing Interaction between Services and Their Agents)
支持多重复消息机制将提高服务的自治性(第一原则),因为服务不再依靠用户是否做正确的事情,还有一些方面需要注意的:
• | 使用模式的服务将肯定会使用大量的存储空间来容纳缓存中的响应。 |
• | 由于缓存的管理,势必会对服务的运行性能造成较大的影响。 |
模式#3:预留(Reservation)
这个模式的样本代码可以下载获得。
• | 上下文:
| ||||||||||||||||
• | 问题:
| ||||||||||||||||
• | 影响:
| ||||||||||||||||
• | 解决方法:
|
图2 预留模式
• | 征兆和结果:
|
预留ID和时间终止戳应该是验证请求模式的一部分,将预留过程与实际业务过程联系起来。对于每个预留请求,服务为之产生预留IDs又生成时间终止戳,使服务能周期性地检查和终止不确定的预留,这种模式可以与多消息模式相结合,并进一步使服务与重复预留请求相隔离。
还有一些需要注意的方面:
• | 处理预留请求的业务规则必须明确定义,预定过多将如何处理? |
• | 我们有点习惯于两个阶段的提交模型,并且经常试图在不适合的地方应用它(比如,长时间运行过程)。长时间运行过程必须保持原子过程的一致性,在这种长时间运行过程中的工作隔离不是一个简单的任务或一个模式,比如预留就是提到此论题的简单尝试。 |
SO的四个原则提供了一系列基本的规则,它们能引导服务发展的方向,这篇论文所列举的模式和反模式都用来说明这些原则如何对服务设计产生的影响,我们也提供一些额外的指导来确保你们将来的服务设计和发展方向将会取得成功:
• | 当代收服务时,将业务过程在已有文档和经确认的业务事件的基础上进行模型化。 |
• | 服务接口的灵活性时很重要的,但也要避免太过于灵活而导致服务协约变得模糊不清。 |
• | 不要期望用户始终做正确的事情,如果服务需要用户按照先前定义好的方式去执行一系列的步骤,那么需要寻找一些模式(比如,预留)去维护加强这些步骤。 |
• | 不能将服务和相关的资源处于不一致的状态。 |
还有许多其他的设计理念与网络服务相关,这系列中以后的论文将会阐述有关于版本更新、服务因素和策略驱动的服务配置等方面。
更多精彩
赞助商链接