SOA实现:服务设计原则(一)

日期: 2008-08-03 作者:David J.N. Artus 来源:TechTarget中国 英文

  通过将这些原则应用到面向服务的体系结构设计,可以帮助通过IT灵活性实现业务灵活性远景。


  引言


  面向服务的体系结构(Service-Oriented Architecture,SOA)提供了支持业务灵活性的IT灵活性远景。在本文中,我们将重点讨论IT灵活性的两个特定方面:流程实现的分离和简化。如何说明和实现各个服务对IT灵活性的这些方面有很大的影响,因此也对业务灵活性有很大的影响。我们此处的目标是提供支持SOA远景的服务说明和实现指南。本文的论述结构如下:


  ·首先,我们将描述将在其中说明和实现服务和服务操作的上下文环境。我们将考虑SOA基础结构的职责和服务的职责。
  ·接下来,我们将讨论适用于整个服务(而不是各个操作)规范的设计原则。
  ·最后,我们将说明适用于各个服务操作的设计原则。


  我们在文中所给出的设计原则旨在通过改善流程实现的分离和简化来提高IT灵活性,因此,我们将通过对这些理念进行更为深入的分析来完成我们的介绍。


  分离


  SOA原则非常强调将服务使用者和服务提供者分离开来,关于此类分离实际的含义,有很多不正式但非常有用的约定。分离背后的一个基础概念就是,对服务提供者的修改不应要求在服务使用者中进行相应的修改。例如,将当前在特定操作系统上运行的服务重新部署到另一个平台的决定完全可能不要求对服务使用者进行更改。一个主要的SOA指导原则就是,要减少使用者和提供者之间的依赖关系。


  分离应用于技术层面,强调Web服务和异步消息交付之类的技术,以允许使用者独立于服务提供者选择实现和可用性选项。我们还可以通过各种方式让SOA基础结构实现技术分离,如:



  表1. 分离技术


  服务应该设计为与要部署到其中的SOA基础结构兼容,特别是,服务应确保避免不必要的耦合。举个相反的例子,有状态服务接口将倾向于通过将使用者与特定提供者实例关联来增加使用者和提供者间的耦合。


  分离的概念也同样适用于非技术的业务层次。服务使用者应该尽可能与服务提供者实现的业务逻辑细节分离。为了实现此类分离,同样也需要进行细心的设计。在下面的服务设计原则部分,我们将讨论将服务接口表述为有意义的业务操作(而不是细粒度的原语方法)的好处。


  流程实现


  在SOA中,我们通过对各个服务进行编排(通过编程方式或使用基于业务流程执行语言——Business Process &#101xecution Language,BPEL——的工具)来实现业务流程。如果要实现SOA远景,则必须简化创建和修改流程的任务——即,服务编排任务。


  业务流程编排的目标在于实际而有效地实现所需的业务逻辑。流程实现人员所面临的问题包括:


  ·选择恰当的服务操作
  ·确定使用正确参数调用服务的顺序
  ·处理各种可能的响应,包括错误响应


  应当清楚,服务设计的质量对编排简化有很大的影响。有关服务、操作和参数的名称和数量以及文档质量和服务操作之间的相互依赖程度——所有设计问题——的决策都可能给编排带来很大的影响。


  SOA设计原则


  这一部分是有关整个SOA系统的指南,代表了在建立系统时需要进行决策的各个方面。您将向设计人员和实现人员提供哪些规则和指导方针?您的SOA基础结构将提供何种功能?我们将给出一系列建议设计原则,但每个都是设计过程中的一种折衷做法。您的企业可能有具体的要求,而需要选择与我们提供的常规建议不同的选项。我们提出设计原则的目的在于标识需要进行决策的方面;而此类决策则是架构设计人员的责任。我们并不认为所提出的设计原则非常全面;在您的企业中实现SOA时,很有可能会采用其他原则,我们非常希望您能将这些设计原则反馈给我们。


  SOA要求一致性


  有很多可用于创建、发布、发现和调用服务的候选技术。SOA应提供一个参考体系结构,以指定服务提供者和使用者将使用的特定机制;我们应以在SAO所有参与者间实现一致性为目标。此类一致性可以减少开发、集成和维护工作。


  如果需要使用参考体系结构之外的元素,我们推荐使用补充性方法。例如,假如我们为服务发布和发现选择的机制是UDDI,但某个特定的开发团队已在使用一个基于其他存储库技术的开发流程,此时该如何处理呢?我们将选择投入精力将该团队的服务同时发布到两个存储库。这样,现有的服务使用者就可以使用其熟悉(但可能并不标准)的存储库了。而运行于公共SOA基础结构上的使用者则可以为所有服务使用标准存储库——例如UDDI。


  SOA简化开发


  我们希望任何企业级的SOA基础结构都具有可伸缩性和弹性;还应包含行业级的企业服务总线(Enterprise Service Bus,ESB)和安全技术。或者,换种说法,以SOA为目标的服务和流程的开发人员可利用成熟的中间件,依赖SOA基础结构提供问题的解决方案,如身份验证、消息转换和可靠消息交付。


  这些中间件功能的提供应以一个重要的原则为基础:服务和流程开发人员应远离中间件实现的复杂细节。我们的理想目标是,在我们的SOA环境中工作的开发人员应只需要业务领域的相关知识和基本的编程技巧。


  我们可以通过各种方式实现此目标,如下所述:


  ·声明技术:J2EE编程模型就是使用声明技术提供应用程序逻辑和中间件配置分离的一个例子。例如,应用程序组装人员通过在部署描述符(而不是代码)中添加相应条目来应用EJB方法角色的安全授权;然后部署人员会将这些角色映射到用户和组。请注意,部署人员无需编写任何授权代码。
  ·抽象: 在某些情况下,SOA基础结构中可以提供API,以用于特定的用途。例如,SOA基础结构可以提供错误报告和审核机制。在设计此类API时应非常小心,要注意其易用性。我们应优先考虑声明技术,而不是对这些机制进行编程配置。同样,在标准API可用时(例如Java日志API),我们应通过这些标准API公开SOA基础结构功能,而不是采用自己开发编写的方式。
  ·代码生成: 在无法避免代码复杂性的地方,可以使用代码生成技术。例如,Web服务描述语言(Web Services Definition Language,WSDL)就可以为开发人员隐藏SOAP、HTTP和JMS的复杂细节。这是通过组合用WSDL表示的可由计算机处理的接口定义和可从WSDL生成相关调用代码的语言特定实现的工具来实现的。
  ·工具:在不可避免SOA基础结构的细节进入开发人员代码的情况下,我们可以通过使用合适的工具扩展开发环境来减少开发人员工作的复杂性。IBM Rational Software Development Platform产品所提供的基于Eclipse的环境可使用自定义插件、代码片段和用户指南轻松地进行扩展。
  ·模型驱动的开发:模型驱动的开发技术可以被视为前面两种方法的特定复杂组合,同时利用了工具和代码生成功能来简化开发体验。开发人员生成统一建模语言(Unified Modeling Language,UML)模型,此类模型可转换为相应的代码,其中包含利用SOA基础结构所必需的代码。


  总之,在定义面向服务的体系结构及其基础结构时,我们必须特别注意开发人员的需求。当为开发人员提供指南,以告知他们应如何开发或使用服务时,我们应该寻找可促进这些指导方针遵循的机制。一个总的原则是“只要可方便完成所需的工作,就说明方法是正确的。”换句话说,遵循相关指南应当为阻力最小的方法。SOA内的控制对其成功甚为关键。


  从开发人员的角度而言,他们有责任了解SOA基础结构和指南,并积极使用指南,而不要尝试进行规避。


  服务具有标准的、经过正式定义的可由计算机处理的接口


  了解了工具和代码生成在SOA实现中可扮演重要角色之后,我们现在要强调使用可由计算机处理的接口的重要性。当使用定义良好的可由计算机处理的语言描述了接口时,实际上就为各种工具支持功能提供了支持。我们希望改善分离状况,因此我们强烈建议使用WSDL之类正式定义的开放标准语言,而不要使用专用格式。


  可由计算机处理的方法的概念应该从服务接口描述(如WSDL)扩展到所有其他形式的声明信息或元数据。只有同时强调声明技术和可由计算机处理的元数据,才能将其相关的复杂性从业务应用程序开发人员转移到基于标准的中间件中。新兴的WS-Policy之类的技术在支持此方法方面充当着重要的角色。


  服务应设计为可重用


  服务设计人员应该记住,他们所开发的任何服务都可能成为可重用资产。设计人员不应只关注服务的最初使用者的需求,而应该进行更为广泛的业务分析,以确定更全面的需求。我们建议,设计人员应考虑服务可能的发展方向:


  ·设计必须能适应不断增加的吞吐量;如果服务在使用服务的数量增加的情况下仍可成功运行,那么使用率也会成级数递增。
  ·如果使用服务的数量增加,则数据量和并发数据访问模式可能会与最初投入使用时的情况大为不同。
  ·我们必须对服务请求的未来增长进行预计;新使用者可能需要其他的功能,或者需要对现有功能进行更改


  文本其余部分所讨论的很多设计原则都与确保服务的可伸缩性和可维护性密切相关。需要提醒一下:可能会由于考虑了潜在的重用而采用不恰当的设计方法对服务进行设计,从而导致实现“过当”。我们鼓励将最初的重点放在服务接口设计上,以确保其支持可伸缩性;我们的设计原则可帮助做到这一点。然后生成一个该接口的战术型实现,要求足以满足目前已知的需求。假如该接口设计良好,应该可以在出现相关需求时替代伸缩性更好的实现。


  服务设计原则


  我们曾说过,服务是其接口采用某种一致认可的格式发布的服务操作的逻辑分组,那么我们接下来将讨论适用于整个服务的设计原则。在下面的服务操作设计原则中,我们将讨论各个操作的设计。


  命名服务时应以最大化易用性为目标


  我们在选择服务、操作、数据类型和参数的名称时有一个指导原则:希望最大化服务的易用性。我们希望帮助流程开发人员标识实现业务流程所需的服务和操作。因此,我们强烈建议使用服务使用者专业领域内有意义的名称,优先选用业务概念而不是技术概念。


  我们的建议就是:应使用名词对服务进行命名;而应使用动词对操作进行命名。


  比较清单1和清单2中所示的两个服务定义。我们使用简化的伪代码来减少编程语言“簇”。


  清单1. 使用动词短语和IT构造的服务定义


 ManageCustomerData {
 
  insertCustomerRecord()
  
  updateCustomerRecord()
  
  // etc …
 }
 
  清单2. 使用名词和动词短语及业务概念的服务定义


   CustomerService {
 
  createNewCustomer()
  
  ChangeCustomerAddress()


  CorrectCustomerAddress()
  
  EnableOverdraftFacilityForCustomer()
  
  // etc …
  
 }


  请注意清单1中的定义是如何使用IT概念进行表述并同时为服务和操作使用动词短语的。在清单2中,服务表述为名词,而操作则使用具有清楚的业务含义的动词短语进行命名。我们认为第二个示例的易用性更好一些。此外,在第二个示例中,服务的业务用途非常清楚,而不单是仅仅指示其输出。因此,我们不使用“update customer record”(可以为出于任何原因进行的任何更新),而使用“enable overdraft facility”。与此类似,在客户搬迁时,我们使用“change customer address”方法更改客户地址;而在希望更正无效数据时使用“correct customer address”更正客户地址,因为这样很容易看出这两个操作采用了不同的服务逻辑。


  如果采用业务概念表述服务和操作名称,则必须密切注意如何确定这些名称。这就非常需要有一个正式的术语词汇表,可以通过业务分析活动得到这个词汇表。词汇表应该有一个正式的所有者。


  服务应具有精心选择的粒度


  粒度 一词在SOA相关讨论中有多种不同的用法。在本文的服务设计讨论中,我们考虑的是服务本身的粒度,即服务应该包含的操作数量。


  没有可用于确定服务粒度的简单启发式方法。我们将提供两个在设计服务时应该考虑的因素的示例加以说明:


  ·服务将通常作为测试和发布的单位。如果粒度过粗,而将大量操作分组到单个服务中,则可能将增加服务的使用者。因此,如果我们对服务的某些方面进行更改(可能仅为了其中一些使用者的利益),则必须重新发布整个服务,从而可能影响所有使用者。
  ·服务使用者所面临的一个挑战就是找到正确的操作。通常,使用者需要浏览服务列表,然后在标识了合适的服务后浏览服务操作列表。我们认为,服务粒度的两个极端——提供仅有几个方法的很多服务,或数十或数百个操作均集中在几个服务中——都将对易用性造成影响。


  这表明,在选择服务粒度时,我们可能需要在多个因素间进行折衷,如可维护性、可操作性和易用性。任何给定的SOA都应向服务设计人员提供指南,以便确定此类折衷方案。


  服务应是内聚而完整的


  既然认识到了在确定服务粒度时需要考虑周全,那么在确定哪些操作应组成服务时有什么注意事项呢?我们认为有两个对象设计概念很有用:内聚性和完整性。我们可将这些概念应用于服务接口。


  我们希望创建功能内聚的接口,一组操作由于其功能相关而聚合到一起。我们发现,当评估内聚程度时,从服务使用者角度看待服务很有用。通过使用者的视角,我们会将重点放在服务的功能上。将此方法与使用以下内聚标准进行对比:


  ·我们可以考虑基于功能实现的内聚性进行决策。是否应由于操作使用相同的算法分组到一起,或者由于均是使用相同主机上的CICS事务实现的而将其分组到一起?这些是实现细节,不应影响接口设计。
  ·可以使用时间内聚性原则,即,将在短时间内一起使用的操作分组到一起,例如,RetrieveCustomerDetails、CheckCreditRating、createLoanFacility和TransferFunds操作都可能在金融业务流程中依次出现。不过,时间内聚性并不意味着这些操作应该由同一个服务提供,CheckCreditRating和TransferFunds就缺乏功能内聚性。


  使用名词-动词对服务和操作进行命名的规则可以帮助我们将重点放在服务接口的功能内聚性上。我们可以问这样一个问题“这个动词是否是该名词所进行的操作?”


  我们的第二个对象设计概念是完整性概念。在为已知使用者创建服务时,完整性的问题尤为值得注意。在这种情况下,我们通常会将重点放在满足所知的使用者需求上。请务必记住,服务应该为可重用的,因此需要考虑将来的使用者的可能需求。举个简单的例子,假如有个名为CellPhone的服务提供create、update、Query、delete和Deactivate等操作。我们完全可以想象会需要对弃用的手机进行重新激活,因此应决定是否也应提供对称的Activate方法。


  通过判断,我们应该应用完整性规则。如果不知道使用者需求,则可能很难提供正确的功能,因此就有可能存在将开发和测试工作浪费在提供将不会使用的操作上的风险。


  服务应对实现细节进行封装


  另一个对象设计原则(封装)也适用于设计服务接口。我们封装服务实现的细节——所用的算法和资源——的动机在于增加服务使用者和提供者之间的分离,从而为将来扩展提供灵活性。


  服务适应多种调用模式


  WebSphere等提供的Web服务技术允许进行更高层次的封装。服务使用者通过使用各种调用模式,可以使用完全相同的代码技术来调用Web服务,如以下这些模式:


  ·使用SOAP over HTTP的传统同步调用
  ·使用SOAP over JMS的基于消息的异步调用
  ·使用Java过程调用的本地调用


  不过,虽然Web服务基础结构可以封装调用的细节,从而简化代码,但服务设计也应对调用方式加以考虑。对比一下本地调用和远程调用。与清单3所示内容类似的服务设计可以提供有价值的业务功能,但却不适合在很多SOA环境中部署。


  清单3. 繁忙型服务接口


 LibraryCatalogService {
  // search operations elided
  
  String getBookTitle(String isbn)


  String getBookAuthor(String isbn)


  Date getBookPublicationDate(String isbn)


  // further operations elided
 }

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐