扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
面向服务的体系结构(Service-Oriented Architecture,SOA)正快速成为很多企业中的主要体系结构样式。构建 SOA 解决方案的主要目的是通过松散耦合其系统来对企业进行武装,从而能更好地响应业务需求。在 SOA 解决方案内设计 Web 服务的主要目标之一是支持快速构造业务流程。您还希望加速企业内以及与外部业务合作伙伴的应用程序集成。
在实现 SOA 解决方案的上下文中,服务接口的结构非常重要。设计糟糕的服务接口可能会极大地导致使用此接口的很多服务使用者应用程序的开发过程变得非常复杂。从业务角度而言,设计糟糕的服务接口可能使得业务流程的开发和优化变得复杂。相反,设计良好的服务接口可以加速开发计划的执行,并对业务级别的灵活性起到促进作用。
Web 服务从本质上就非常适合用于构造 SOA 解决方案。Web 服务领域的很多现有和未来的行业标准可确保互操作性,此类标准包括 SOAP、Java API for XML-based RPC (JAX-RPC)、WSDL 和 WS-* 规范等等。各种流行的开发环境(如 IBM® Rational® Application Developer 和 IBM WebSphere® Integration Developer)中均包含了基于标准的工具,从而能够加速 SOA 项目的进行。
本文的重点是服务接口设计抽象层面的东西:
基于 XML 和 Web 服务的编程模型和开发工具定义了三种构建 Web 服务的方法:
自底向上
各种先进的集成开发环境(Integrated Development Environment,IDE)提供了用于从现有代码(如 Java™ 或 COBOL)创建 Web 服务实现的工具。使用此方法时,开发人员通常将选择现有的 JavaBean 或 EJB 组件,并调用向导来生成 WSDL 文件,以用于将 Bean 或 EJB 作为 Web 服务调用。
自顶向下
使用此方法时,开发人员将首先使用 WSDL 和 XML 模式(XML Schema,XSD)构造定义 Web 服务接口,然后为服务生成框架实现代码。接下来,开发人员将完成框架服务实现。大多数先进的 IDE(如 Rational Application Developer V6 和 WebSphere Integration Developer V6)都为此方法提供工具支持。
中间相遇
此方法对前面两个方法进行了结合。开发人员首先使用 WSDL 和 XSD 定义服务接口,并为服务生成框架实现。如果有必要,开发人员还可以使用自底向上技术来通过方便的应用程序编程接口(Application Programming Interface,API)公开现有代码。然后开发人员将编写在新设计的接口和旧接口之间进行转换的代码。
很多熟练的 Java 开发人员喜欢使用自底向上技术来加速 SOA 项目中的 Web 服务开发。他们将首先用 Java 开发新服务的实现,然后将使用强大的代码生成向导来为这些服务创建 WSDL 接口。尽管此方法可以加速各个服务的实现,但这对整个 SOA 项目通常都意味着问题。
之所以出现问题,是因为自底向上生成经常会得到无法重用的类型定义以及多个定义为表示语义等效信息的类型。
最佳实践:使用自顶向下和中间相遇开发方法,而不使用自底向上技术。使用 XSD 和 WSDL 设计您的服务接口,然后生成框架 Java 代码。
当存在现有遗留代码(例如 JavaBeans、EJB、COBOL 等)时,就适合使用自底向上开发方法。采用此方法时,应该仔细地复查现有类的接口,然后再生成 WSDL 接口。如果 Java 接口仅包含任何以下内容,则可以将其视为弱类型:
应该考虑对遗留代码进行重构,以确保接口为强类型;或构建中介,以使用强类型接口包装弱类型接口。
Java 与 WSDL 的比较
可以使用 Java 或 WSDL 描述您的服务接口。Web 服务相关的规范(如 SOAP、JAX-RPC 和 JAX-B)定义了映射,以说明采用 Java 定义的类型如何映射到 WSDL/XSD 中以及如何进行反向映射。
最佳实践:使用 WSDL 和 XSD(而不是 Java)描述服务接口。服务接口定义是一种 WSDL 端口类型。
XML 模式规范定义范围比 Java 更广的用于描述消息结构的构造。其中包括各个选择、限制的派生、Annotation 及其他。因此,与采用其他方式相比,使用 WSDL 和 XSD 定义接口并生成框架 Java 代码的方式更好。
WSDL 和 XSD 一起形成了与技术无关的可供 SOA 实现使用的接口定义语言。除了 Java 之外,WSDL/XSD 接口定义可以用于生成采用很多语言(例如 COBOL 和 C++)的框架实现。
接口粒度
服务接口通常应该包含多个操作。定义为单个服务接口一部分的操作应该从语义上相关。仅包含单个操作或少量操作的大部分服务都表明服务粒度不恰当。反过来,采用很少的服务(或者单个服务)来包含大量操作也同样表明服务粒度不恰当。
让我们通过一个例子来更好地了解应该如何做出关于服务接口粒度的决策。SOA 项目中最常遇到的一个情况就是将现有事务作为 Web 服务公开。在此示例中,现有的 S390 大型机承载 CICS 区域,其中运行着用于管理客户信息、产品定价和产品可用性的很多 COBOL 事务。
最佳实践:服务接口(WSDL 端口类型)通常应该包含多个操作。定义为单个服务接口一部分的操作应该通过其操作的数据从语义上相关。
每个 COBOL 事务都可以公开为单个 Web 服务操作。例如,我们可以定义名为 MyS390Service 的服务,该服务具有单个接口,为该大型机上运行的所有 COBOL 事务定义操作。这样会产生具有数十个操作的接口,客户机应用程序可以使用此接口调用该系统上的任意事务,而不受事务是否与客户管理或产品定价相关的影响。
此方法使得服务难于理解,因此会难于在业务流程中重用——最终导致出现同一个服务存在很多个版本。(将在后续文章中讨论有关服务版本控制的更多信息。)我们鼓励开发人员不要仅仅基于其目标物理系统对操作进行分组。
另一个方法涉及到为系统上的每个事务定义新接口。这样会得到很多接口,而且最终会存在使用这些接口的很多服务。服务大量增加反过来会导致服务治理问题,从而使得更难获得有效的代码重用。
最好的方法是,通过对语义相关的事务进行分组来定义接口(WSDL 端口类型)。在我们的示例中,对客户信息进行操作的 COBOL 事务对相同的数据集进行操作,因此是语义相关的。
如果客户信息驻留在多个企业信息系统(Enterprise Information System,EIS)中,而不是如前例中那样驻留在一台大型机上,则应该首先定义物理系统特定的接口,以对客户信息相关的事务进行分组,然后将这些接口聚合为单个接口,以便进行客户信息管理。图 1 显示了接口聚合方法。
最佳实践: 如果相关的信息驻留在多个 EIS 上,则应该首先定义物理系统特定的接口,以对信息类型相关的事务进行分组,然后将这些接口聚合为单个接口。
图 1 中提供了系统特定的接口聚合为通用接口的示例。EIS1 提供对客户信息的访问,如地址等。EIS2 包含客户帐户数据。通用 CustomerInfo 接口对两个 EIS 特定的接口的操作进行合并。
图1. 将系统特定的接口聚合为通用接口
操作签名本部分将讨论以下操作签名语义:
此部分中的最佳实践指导方针可以帮助您设计可供方便重用和包含到业务流程中的服务。
同步接口与异步接口的比较
WSDL 端口类型可以包含一个或多个操作。操作可以为单向的,也可以为请求-响应。单向操作可以定义请求消息,但不能定义响应消息。不可能为单向操作定义错误消息。(有关 WSDL 的更多信息,请参见参考资料。)
只要客户机应用程序调用单向操作(例如使用符合 JAX-RPC 标准的 Java 代理),会立即将控制返回给调用客户机应用程序线程。客户机应用程序没有办法知道消息是否成功交付(甚至不能知道是否成功发出)。
调用应用程序对此可能接受,也可能不接受。如果可接受,则应用程序可以调用单向操作,并依赖于面向消息的中间件(如 SIBus 或 WebSphere MQ)来确保将消息交付到预期的目的地。如果不能接受,则应用程序可以使用同步调用技术来实现异步语义(将在稍后对此进行说明)。
请求-响应操作可以定义一条请求消息、一条响应消息和任意数量的错误消息。当客户机使用异步协议(如 HTTP)发送请求消息时(例如,符合 JAX-RPC 标准的 Java 代理),代理将阻止调用线程,直到从服务接收到响应或错误为止。
错误将提供有关在服务调用期间出现的故障的错误信息。在很多处理方案中,此信息与成功调用期间返回的数据一样重要。
服务通常由面向最终用户的应用程序(需要向最终用户提供关于错误的信息)调用。很多业务流程需要直接对使用同步绑定调用的服务返回的错误信息进行分析,以便恰当地定向后续处理。在这种情况下,应该始终尽量利用使用错误的请求-响应操作设计接口。(将在本系列的后续文章中更详细地对错误和错误处理进行讨论。)使用采用请求-响应交互模式的同步协议,并定义最终用户可理解的错误。
最佳实践: 在服务接口中定义错误,并在服务实现中进行使用。
可以采用两种不同的方式进行异步调用:
服务请求者并不等待或需要响应。应用程序或业务流程直接发出要交付给预期的目的地的消息并继续进行处理。
服务请求者发出请求消息,并随后向服务轮询响应,或者向请求者发出回调。
最佳实践:设计新服务时,不要在单个接口(WSDL 端口类型)中混合使用同步调用和异步调用语义。如果同时支持两个语义具有优势,则请为同步调用和异步调用定义独立的接口。
清单 1 显示了使用同步操作来实现异步调用语义的示例。事务 debitAccount 不必返回值。通过向操作添加返回值,可以在客户机应用程序中进行错误处理。
String transNumber; Try { transNumber = debitAccount(amount); } catch (SystemFault sysFault) { System.out.println(sysFault.getError()); // React to system level fault } catch(BusinessFault busFault) { System.out.println(sysFault.getError()); // React to business level fault } |
在上面的示例中,调用应用程序使用将事务数量作为响应消息返回的请求-响应操作发出请求消息。调用线程将阻止,直到接收到确认信息,表明消息已经成功交付到其预期目的地。如果遇到了问题,将可能由服务提供者或 Web 服务调用引擎引发系统级别和业务级别的错误,并由调用应用程序捕获(同步行为)。
调用应用程序会稍后使用事务数量来轮询服务提供者接口,以获取业务响应消息(异步行为)。当调用应用程序对响应不感兴趣时,还可以返回 Boolean 值来直接指示是否成功,从而说明请求消息是否成功交付。
有状态接口与无状态接口的比较
服务之间的交换可以为有状态,也可以为无状态。当服务提供者保留关于在之前的操作调用期间服务使用者和服务提供者之间交换的数据的信息时,则服务之间进行的是有状态(或对话型)交换。
例如,服务接口可以定义名为 setCustomerNumber() 和 getCustomerInfo() 的操作。在有状态交换中,服务请求者将首先调用 setCustomerNumber() 操作,并同时传入客户编号。服务提供者在内存中保留客户编号。接下来,服务请求者调用 getCustomerInfo() 操作。服务提供者将随后返回与之前调用中设置的客户编号对应的客户信息响应。
在无状态交换中,服务提供者定义 getCustomerInfo() 操作,使其接受客户编号作为输入参数。服务提供者并不需要定义 setCustomerNumber() 操作,服务请求者也不需要对其进行调用。每个操作调用代表一个独立的事务,该事务具有相关请求消息(包含完成此操作所需的所有必要信息)。
在构建 SOA 的过程中,将无状态接口视为最好的选择。无状态接口可以方便地供很多服务使用者应用程序重用,可以采用最适合每个应用程序的方式管理状态。
最佳实践:设计采用无状态交互的服务接口。传入操作的请求消息应该包含完成该操作所必要的所有信息,而不受到调用其他接口操作的顺序的影响。
Header 与有效负载的比较
请求消息包含将由服务用于执行操作的业务逻辑的数据。这些消息也可以包含与事务关联的系统级别处理(而非事务执行的业务逻辑)相关的数据。此类数据的示例包括:
类似地,服务操作发出的响应消息可以包含系统级别的数据,如:
计算响应时间
这些系统级别的数据必须由服务提供者应用程序(除了负责处理业务级别的数据外)或企业服务总线(Enterprise Service Bus,ESB)基础设施进行处理。在构建 SOA 解决方案的过程中,较好的做法是,恰当设计服务接口的结构,以将系统相关的数据与业务相关的数据分开处理。
SOAP 规范规定,SOAP 消息可以包含 SOAP Header、主体以及任意数量的用户定义 Header。应该定义和使用自定义 Header,以用于承载特定于您的业务或项目的系统相关信息。避免将系统相关的信息放置到消息主体中。这样就允许 ESB 基础设施在不用解析消息主体的情况下处理消息(性能密集型操作)。
最佳实践:定义和使用自定义 Header,以用于承载特定于您的业务或项目的系统相关信息。避免将系统相关的信息放置到消息主体中。
总结
SOA 允许企业以灵活的方式发展其 IT 基础设施。Web 服务提供用于实现 SOA 的理想技术。设计良好的服务接口可以对 SOA 的实现起到促进作用,而设计糟糕的接口则会极大地使得工作复杂化。在本文中,我们了解了服务接口高级设计的最佳实践。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。