探究跨平台Web服务集成所面对的常见的互操作性难题的根源。该系列文章中的第3部分描述了J2EE技术与.NET之间可以导致Web服务互操作性困难的不同的命名约定。
引言
正如Java包通常用来保证Java类,使其只能存在于不同层次的命名空间中,这样就可避免类、方法等等之间的命名冲突,XML命名空间也是为相同的目的而服务于Web服务。它限定XML元素或属性的名字并帮助它们避免命名冲突。XML命名空间是基于URL应当是全局唯一的基础之上的。然而,解释URL的方法及在本机代码的映射对于不同的平台来说是不同的。通常这些不同之处是微妙的,但如果开始时不解决这些的差别,到后来有可能会很难解决。
我将在下面的部分当中讨论几个与命名空间有关的互操作性问题,包括:
·使用相关的URI引用
·使用共享通用域名的唯一的URI
·数组类型中的命名空间问题
在WSDL中用相关URI引用作为命名空间声明
在命名空间声明中并没有严格禁止相关URI引用,但在规范中也没有为它们提供解释。如果WSDL文件是从J2EE Web服务中生成的,这通常不是一个问题,因为目标命名空间是从Java包名字派生而来,并且工具(例如,Java2WSDL)自动将它们与模式联系起来。但是在Microsoft .NET Web服务实现当中,如果您允许.NET框架生成WSDL文件,那么目标命名空间就会直接从您在代码中的定义生成。您可能会经常看到命名空间属性被分配到相关URI的情况。清单1显示了从库存中取得产品列表的C# .NET Web服务代码。
清单1. 有相关命名空间URI的库存Web服务
[WebService(Namespace=”services.inventory”)]
public class GetProductsService: WebService
{
public struct Product {
public string name;
public int qty;
public float price;
}
[WebMethod]
[XmlInclude(typeof(Product))]
public Product[] listProducts()
{
Product[] products =
getInventory(); // getInventory() is a private method
to retrieve all products
return products;
}
}
在清单1中,Namespace=”services.inventory”属性在WSDL文件中的结果是targetNamespace=”services.inventory”。结果,所有在本地定义的元素、类型及属性均被映射到命名空间的相关URI services.inventory之下。以下显示了WSDL文档的模式部分:
清单2. 生成的WSDL文件显示了作为targetNamespace的相关URI引用
<types>
<s:schema elementFormDefault=”qualified”
targetNamespace=”services.inventory”
>
<s:complexType name=”ArrayOfProduct”>
<s:sequence>
<s:element maxOccurs=”unbounded” minOccurs=”0″
name=”Product” type=”s0:Product”/>
</s:sequence>
</s:complexType>
<s:complexType name=”Product”>
<s:sequence>
<s:element maxOccurs=”1″ minOccurs=”0″ name=”name”
type=”s:string”/>
<s:element maxOccurs=”1″ minOccurs=”1″ name=”qty”
type=”s:int”/>
<s:element maxOccurs=”1″ minOccurs=”1″ name=”price”
type=”s:float”/>
</s:sequence>
</s:complexType>
<s:element name=”ArrayOfProduct” nillable=”true”
type=”s0:ArrayOfProduct”/>
</s:schema>
</types>
elementFormDefault=”qualified”属性确保targetNamespace限定包括复杂类型Product在内的所有局部声明元素。假设有另一个单位使用相同的相关命名空间实现类似的Product类型。就像当使用IBM WebSphere Studio Application Developer Integration Edition(Application Developer)BPEL设计器从不同的伙伴链接中利用普通复杂类型将Web服务集成到业务流程中时使用wsdl:import及xsd:import一样,从普通模式中将这两个模式导入到WSDL文档中。
在本场景中,如果导入的两个模式有相同的目标命名空间,则很有可能发生命名冲突。用于在另一个平台上构建集成的工具必须确保相关URI是基于RFC2396标准文档树中的基本URI的。然而,在WSDL文档中基本URI并没有定义完善,默认的基本URI的解释取决于应用程序。最好的习惯是始终使用它自己的组织域名来确保命名空间唯一。
共享通用域名的唯一命名空间URI
有人说命名空间污染是软件工程中最糟糕的污染。每个组织都有不同的命名习惯,所以工具通常生成的Web服务存根代码在另一个平台上的WSDL命名空间声明中或许有不同的解释。Web Services Interoperability Organization(WS-I)规范已经在消除命名空间声明中的模糊点方面有了重大改进,并且正在向统一命名空间解释方向前进,但其中仍有一些不足之处。
考虑两个假设,小型银行分部及大型银行投资分部的.NET Web服务。一个为客户创建校验帐户,另一个创建投资帐户:
清单3. .NET中的零售AccountService
namespace Retail
{
[WebService(Namespace=”http://bigbank.com/retail”)]
public class AccountService: WebService
{
public struct Customer {
public string name;
public string address;
}
[WebMethod]
public bool createCheckingAccount(Customer customer)
{
return true;
}
}
}
清单4. .NET中的投资AccountService
namespace Investment
{
[WebService(Namespace=”http://bigbank.com/investment”)]
public class AccountService: WebService
{
public struct Customer {
public string name;
public string address;
public int collatoral_amt;
}
[WebMethod]
public bool createInvestmentAccount(Customer customer)
{
return true;
}
}
}
上述两个帐户服务有相同的域,但有不同的分支:http://bigbank.com/retail及http://bigbank.com/investment。由于不同的需求,零售AccountService中有一个比投资AccountService中稍有不同的Customer复杂类型。两个AccountService类文件都命名为AccountService.asmx。在.NET中这不会有问题,而实际上在.NET中是完全顺乎其理的,因为两个帐户服务都被限定为不同的URL,他们都准确的反映出他们的域名及分支名。
现在,如果您准备在Application Developer中创建客户端项目并试图集成两个Web服务,情况就要有所变化。在Java代码中,当创建Web服务客户端时,包名是基于命名空间的域名的。http://bigbank.com/retail及http://bigbank.com/investment有相同的域名:http://bigbank.com。因此,生成的复合数据类型及两个Web服务的代理将有相同的包名:com.bigbank。因为我们将两个.NET Web服务命名为AccountService.asmx,并且两个不同的Customer结构类型有相同的名字,结果很明确:当Application Developer生成代理文件时,为AccountService客户端生成的存根文件(AccountService.java、AccountServiceLocator.java、AccountServiceSoap.java、AccountServiceSoapProxy.java及AccountServiceSoapStub.java)将重写先前生成的同名文件。根据后期生成的结果,现在只有一个Customer复合类型而不是两个。
这个命名冲突是在.NET及Java技术中命名习惯的不同而导致的。正如您所看到的,命名空间声明中的唯一URL仍不能完全避免命名冲突。解决方法是保证每个Web服务拥有唯一的域名。上面的两个AccountService可以使用http://retail.bigbank.com及http://investment.bigbank.com/分别作为命名空间限定符,从而使域名唯一。
如果没有经验改变现有.NET Web服务中的命名空间声明的话,Application Developer中的Web服务客户端代理生成向导也会提供一个选项来定义由命名空间到包的自定义映射,如图1所示。
图1. 在Application Developer中定义由命名空间到包的自定义映射
命名空间及共享XSD模式
在J2EE技术及.NET中,在众多的Web服务中共享XSD模式是非常普遍的。实际上,共享XML模式是模块化设计及可重用性考虑的最佳实践。XML标签:import及include的使用也正是由于此目的。例如,您可以为货物仓库的Product类型设计模式,如清单5中所示:
清单5. Product类型
<?xml version=”1.0″ encoding=”UTF-8″?>
<schema targetNamespace=
”http://catalog.warehouse.com” >
<complexType name=”Product”>
<sequence>
<element name=”_name” type=”string”></element>
<element name=”_int” type=”int”></element>
</sequence>
</complexType>
</schema>
Product类型被限定于命名空间http://catalog.warehouse.com之下。可以为其他Web服务将其导入到WSDL中管理库存。假设订货部门有C#的Order Web服务实现,如清单6中所示:
清单6. .NET中的定购Service
[WebService(Namespace=”http://order.warehouse.com/service”)]
public class OrderProductService: System.Web.Services.WebService
{
[WebMethod]
[XmlInclude(typeof(Product))]
public string OrderProducts(Product[] products)
{
int len = products.Length;
//do the order
return “Total number of orders processed: ” + len;
}
}
库存部门使用库存Web服务进行重新进货,它必须重用相同的Product类型,如清单7中所示:
清单7. .NET中的库存Service
[WebService(Namespace=”http://inventory.warehouse.com/service”)]
public class InventoryProductService: System.Web.Services.WebService
{
[WebMethod]
[XmlInclude(typeof(Product))]
public string RestockProducts(Product[] products)
{
int len = products.Length;
//add to the inventory
return “Total number of products added: ” + len;
}
}
现在有三个命名空间:Product类型的http://catalog.warehouse.com、Order Web服务的http://order.warehouse.com/service以及Inventory Web服务的http://inventory.warehouse.com/service。乍一看,似乎没有潜在的命名冲突。依据前两部分:这三个URI有足够的资格,每个Web服务的域名都是唯一的,甚至Web服务类名也是不同的。
但仍然会发生问题。当在清单6及清单7中的两个Web方法中传递数组时会发生问题。
如果您创建J2EE项目集成定购服务及库存服务,并定购一些产品或重新进货某些产品,只有一个Web服务会正确执行。由于接收到产品的空数组,所以另一个服务会悄无声息的失败掉,即使J2EE客户端确实为两个服务都发送了填充了产品的数组也是如此。为什么会这样?
您应该研究J2EE客户端与.NET Web服务之间的SOAP通信来寻找答案。
清单8. .NET定购服务的SOAP请求
<soapenv:Body>
<OrderProduct >
<products>
<Product>
<_name >Computer</_name>
<_qty >10</_qty>
</Product>
<Product>
<_name >Monitor</_name>
<_qty >20</_qty>
</Product>
</products>
</OrderProduct>
</soapenv:Body>
清单9. .NET库存服务的SOAP请求
<soapenv:Body>
<RestockProduct >
<products>
<Product >
<_name >Computer</_name>
<_qty >10</_qty>
</Product>
<Product >
<_name >Monitor</_name>
<_qty >20</_qty>
</Product>
</products>
</RestockProduct>
</soapenv:Body>
比较清单8及清单9中的orders数组及products数组的XML表示。在Inventory服务请求中的数组元素由Order服务的命名空间URI所限定-http://order.warehouse.com/service,所以Inventory Web服务不会看到发送给他的任何产品。
那么,问题的根源在哪里呢?问题在于Application Developer JAX-RPC工具生成的.NET WSDL及帮助类。清单10显示了与ArrayOfProduct[]有关的定购服务WSDL:
清单10. 与ArrayOfProduct类型有关的.NET Order服务WSDL的一部分
targetNamespace=”http://order.warehouse.com/service”
…….
<types>
<s:schema elementFormDefault=”qualified” targetNamespace=
“http://order.warehouse.com/service”>
<s:import namespace=”http://catalog.warehouse.com” />
…….
<s:element name=”Product” type=”s1:Product” />
</s:schema>
<s:schema elementFormDefault=”qualified” targetNamespace=
“http://catalog.warehouse.com”>
<s:import namespace=
“http://order.warehouse.com/service” />
<s:complexType name=”ArrayOfProduct”>
<s:sequence>
<s:element minOccurs=”0″ maxOccurs=”unbounded” ref=”s0:Product” />
</s:sequence>
</s:complexType>
ArrayOfProduct模式位于targetNamespace http://catalog.warehouse.com之中,但它的元素引用s0:Product,其中s0是http://order.warehouse.com/service。
回忆当创建JAX-RPC客户端存根类时,命名空间URI如何映射到Java包名。在com.warehouse.catalog包下面创建了ArrayOfProduct类,帮助类ArrayOfProduct_Helper将它的Product字段绑定到命名空间s0,即http://order.warehouse.com/service上(参见本文中的清单11)。
这并不是好消息。Inventory及Order Web服务的客户端共享com.warehouse.catalog包下面的相同的ArrayOfProduct类,但是它的类字段却绑定到特定的Web服务上。在本例中是Order服务。这正是在清单7中您可以看到的SOAP请求消息,问题的根源就在于此。
在本场景中,没有任何一方违反规范。没有理想的解决方法。首先您必须发现问题,您可以做两件事情来解决这个命名空间绑定冲突的问题:
·当创建第一个客户端代理文件时,将com.warehouse.catalog重命名为另外一个包名。当生成第二个客户端代理文件时,两个文件将都有正确的命名空间绑定。
·当生成客户端代理文件时,使用如图1所示相同的技术。为每个Web服务将http://catalog.warehouse.com映射到唯一的命名空间。
共享XSD模式是一个最佳实践,但Web服务程序员必须注意诸如此类的潜在的命名空间问题,并且要知道当问题发生时如何去修正它。
结束语
在本文中讨论了由XML命名空间冲突所导致的某些互操作性问题。然而,相互作用的Web服务之间的命名空间冲突没有停止于此。仍有许多其他的情况,或是微妙或是稀少,但它会发生。当有大量的Web服务部署在大型的公司环境中时,很难修正命名空间冲突问题。当开发Web服务时,最好要预见并避免不同平台上潜在的冲突。IBM WebSphere Studio Application Developer Integration Edition也提供了强大的重置工具。如果在集成期间真的有命名冲突发生,该工具可以帮助您进行重置。
本系列的技巧讨论了大量的重要议题,可以解决跨平台Web服务互操作性并提供最佳实践,特别是在XML Schema类型的使用、命名空间及Web服务接口绑定方面。在WS-I成员多年的共同努力下,Web服务互操作性是可以实现的了。第三方IDE工具已经成熟,将能更好的集成。但认为有朝一日只是点击几下鼠标即可完成所有的Web服务集成也是不切合实际的,即使通过最成熟及强大的IDE工具的帮助也是如此。毕竟,Web服务是在不同的平台上开发的。即使每个人都依据相同的WS-I规范开发,仍然会有不匹配、曲解及不同的习惯。在产品领域,仍有需要被工程师考虑的许多互操作性问题。某些重要的互操作性问题是:
·错误处理: 预见不同的错误情况及可以发生且被通信方返回的错误类型很重要,并将那些wsdl:faults定义为WSDL中wsdl:operations的一部分。
·安全互操作性: Web服务调用需要签名及加密(WS-Security)。为Web服务执行vendor-neutral及transport-independent安全措施是WS-Security的目标,它也是产品环境中Web服务集成的重要一步。
我将在以后的技巧中讨论其他的互操作性问题。
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
作者
相关推荐
-
SAP收购CallidusCloud 与Salesforce竞争
一直被称为后台办公巨头的SAP现在似乎也想在前台办公大展拳脚。 最新的迹象是SAP收购CallidusClou […]
-
API设计如龙生九子 各不相同
IT咨询管理公司CA Technologies对API产业做了个问卷调查,问卷内容涉及API设计风格以及管理部署的新动向。调查结果表明,JSON与XML可谓两分天下。
-
从头开始实现领域驱动设计
领域描述业务;它是驱动企业的概念和逻辑的集合。如果遵循领域驱动设计(DDD)这一本质,那么领域就是应用程序中最重要的组成部分。
-
走出思维定式 数据库/大型机现代化不再是问题
升级和改变组织的主要利益驱动应用的前景,正处于一个压倒性的位置,所以组织将要面临一系列的改变。