面向服务的体系结构(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 特定的接口的操作进行合并。