深蓝海域KMPRO

设计合同

2002-09-13 14:15

设计合同

 

Scott Seely

Microsoft Corporation

2001年6月6日

简介

我们已经很长时间没有在 At Your Service 专栏中与您进行交流了。为了扭转这一局面,我们专门成立了一个小组来从事这方面的工作,小组成员包括 Matt Powell 和 Scott Seely。我是 Scott,先由我和大家来探讨有关问题。不久前,Mary Kirtland 在让我们认识一下(英文)一文中对我们进行了介绍。不过,既然以后由我们主持本专栏的工作,我们想更为详细地介绍一下自己。

Matt 在 Microsoft 工作了 10 年以上,大部分时间是在 Microsoft 的开发人员支持机构中工作。他坚持认为自己是一个网络协议专家,因为他总是有机会对涉及 DLC、NetBIOS、Winsock、RPC、SNMP、WinInet 和 ISAPI 中的一切提供支持。相对于 Windows? 资源管理器来说,Matt 更喜欢使用 MS-DOS 提示符;在紧急情况下,甚至会使用 EDLIN 来编辑文本文件。业余时间,Matt 喜欢带着他的一家九口去观看西雅图水手队的比赛。他总是坐在第一排,就在 Ichiro 的身后观看比赛,不停挥舞着白毛巾,疯狂地为西雅图水手队呐喊助威。

如果您阅读过本专栏以前发布的文章,您可能会注意到 James Francisco(他以前是我们小组的成员之一)曾经提到我们已经落后于进度表,因为一半开发人员(指我)休假回家去照顾孩子了。3 月 15 日,我去看望了我的女儿 Angeline。她的母亲、哥哥和我都花了四周时间来了解我们这个新家庭成员。在那段时间中,我还为 Prentice Hall 撰写完有关 SOAP 的一书《SOAP:使用 XML 开发跨平台 Web 服务》(英文)。它将在 2001 年 7 月底与读者见面。除了著书之外,我还从事跨平台开发工作。这是因为实际上,我使用过三个非 Microsoft 的 SOAP 工具包:我自己的 SimpleSOAP(英文)、Apache SOAP(英文)和 SOAP::Lite(英文)。

概述

在本专栏中,我们将讨论为 Web 服务设计合同时,需要考虑哪些因素。希望您在提到 Web 服务时,不要再联想到诸如检索股票价格或是查看您所在城市当前的气温这样的事情了。相反,您应该将 Web 服务看作具有相当完善功能的 API 的组件。在本专栏中,我们将讨论如何设计 Web 服务的接口以及满足全球化要求。

定义 Web 服务的接口

现在,通过 Web 服务说明语言 (WSDL) 文件可以告知他人如何访问和使用 Web 服务。最初引入 SOAP 时,曾出现过多种基于 XML 的接口说明语言 (IDL)。所有这些 IDL 识别不同的描述服务方式,并且包含一些特定于它们所依附的 SOAP 实现方案的项目。业界很快意识到必须进行标准化,这样就产生了 WSDL。WSDL 说明了以下项目:

Web 服务可以识别的数据类型

消息架构

Web 服务使用的交换方法(请求/响应、单向、多播等等)

Web 服务的位置

错误信息

标头信息

对 WSDL 的完整说明已超出了本文讨论的范围,不过在本文的末尾,您可以找到一些非常好的 WSDL 背景资料的链接。现在我只想集中讨论如何使用以上项目来定义 Web 服务的接口。

定义接口时,首先应考虑数据类型。应该始终确保您的类型可以重新映射到 XML 架构数据类型。例如,对于字符串,应该使用 xsd:string。构建复杂的类型时,请确保它们最终可以分解为预定义的 XML 架构数据类型之一。例如,您可以通过电子邮件地址和业务电话号码来定义一个联系人。WSDL 文件的类型部分类似于以下内容:

<definitions xmlns:s="http://www.w3.org/2001/XMLSchema"
   
xmlns:s0="
http://tempuri.org/"
   
targetNamespace="
http://tempuri.org/"
   
xmlns="
http://schemas.xmlsoap.org/wsdl/">
   
<types>
       
<s:schema attributeFormDefault="qualified"
           
elementFormDefault="qualified"
           
targetNamespace="
http://tempuri.org/">
           
<s:element name="ContactInfo">
               
<s:complexType>
                   
<s:sequence>
                       
<s:element minOccurs="1" maxOccurs="1"
                           
name="name" nillable="true"
                           
type="s0:PersonName" />
                       
<s:element minOccurs="1" maxOccurs="1"
                           
name="emailAddress" nillable="true"
                           
type="s0:EmailAddress" />
                       
<s:element minOccurs="0" maxOccurs="unbounded"
                           
name="phoneNumbers" nillable="true"
                           
type="s0:PhoneNumber" />
                   
</s:sequence>
               
</s:complexType>
           
</s:element>
           
<s:complexType name="PersonName">
               
<s:sequence>
                   
<s:element minOccurs="1" maxOccurs="1" name="first"
                       
nillable="false" type="s:string" />
                   
<s:element minOccurs="1" maxOccurs="1"
                       
name="middle" nillable="true"
                       
type="s:string" />
                   
<s:element minOccurs="1" maxOccurs="1" name="last"
                       
nillable="false" type="s:string" />
               
</s:sequence>
           
</s:complexType>
           
<s:complexType name="PhoneNumber">
               
<s:sequence>
                   
<s:element minOccurs="1" maxOccurs="1"
                       
name="areaCode" nillable="false"
                           
type="s:long" />
                   
<s:element minOccurs="1" maxOccurs="1" name="prefix"
                       
nillable="true" type="s:long" />
                   
<s:element minOccurs="1" maxOccurs="1" name="suffix"
                       
nillable="false" type="s:long" />
               
</s:sequence>
           
</s:complexType>
       
</s:schema>
   
</types>
   
...

</definitions>

正如您所看到的,所有的复杂类型最终都分解为现有的 XML 架构数据类型。有了这样的限制,其它 SOAP 实现方案就更有可能与您的 Web 服务进行交互。这是因为不同的平台存在着不同的 XML 架构实现方案,只要符合标准并尽量避免使用自己偏爱的个人实现方案,就可以增加 Web 服务的可用性。

下一步,应考虑如何将 Web 服务的功能分解成多个因子。对 Web 服务进行编程时,应该将相关的操作组合到单个端口中。对于收藏服务,我们在设计上将其分解为三个服务:

登录:对被授权者进行身份验证并授予他们一个访问服务时使用的密钥

帐户:处理最终用户的创建和收藏数据的维护事项

报表:处理报表

将功能分成相关的功能块后,在构建特定的功能时,Web 服务的用户就可以很轻松地找到他们所需的功能。编程人员可以了解与特定端口有关的功能,从而使他们的工作变得更为轻松。

这些端口的实现一般驻留在一个对象中。在内部,对象可以共享公共的 Helper 函数。在外部,这些对象不应该依赖于内部状态。我曾观察过新闻组,发现许多人在试图传送 SOAP 对象的指针或引用时都遭遇了失败。为什么他们会失败呢?因为他们设计的东西要求引用指针,但很快他们就会意识到 SOAP 不允许这样做。

如果您陷入了困境,我们向您介绍一个很好的解决方法:使用 in/out 参数。无论对象是 in、out 还是 in/out 参数,它必须能将自己的状态写入 XML。当您指定所需的对象部分时,请分析一下功能真正需要什么数据,并且只需要该信息。这可能会产生巨大的差异,因为在网络上传输数据可能比整个串行化过程花费的时间更多。消息越小,到达目的地所需的时间也就越少。

您可能还希望确保能够监视来回传送的参数。如果使用可以生成 WSDL 的工具(例如 .NET 运行时或 SOAP Toolkit v2 WSDLGEN.EXE 工具),您可能不会意识到自己正在浪费带宽。在构建解决方案时,请花一些时间来了解生成的 WSDL 看起来像什么。看看是否可以找到减少消息往返次数的位置。例如,Visual Basic? COM 对象中作为 ByRef 发送的任何参数(如果您不指定,它们将都是 ByRef)都是一个 in/out 参数。如果使用该对象,但在返回之前不对它进行修改,则应该将该参数更改为 ByVal。同样地,请检查 C++ 对象的 IDL 文件,并确保参数进行了相应的限定。

大多数情况下,Web 服务使用了请求/响应(即传统的 RPC)。可以通过忽略 operation 的 output 元素来指定单向。

请求/响应:

<operation name='DeleteFavorite' parameterOrder='key Username FavID'>
   
<input message='wsdlns:Account.DeleteFavorite' />
   
<output message='wsdlns:Account.DeleteFavoriteResponse' />

</operation>

单向:

<operation name='DeleteFavorite' parameterOrder='key Username FavID'>
   
<input message='wsdlns:Account.DeleteFavorite' />

</operation>

单向消息只是忽略了输出消息。如果有必要,请忽略“输出消息”。这样做可以减少等待的时间,并且可以使服务运行得更快一些,因为客户程序不再等待返回的消息。

因为 WSDL 是 IDL,WSDL 端口代码的唯一要求是它必须正确地响应任何 SOAP 请求。这对您意味着什么呢?假定我们正在使用一个客户端应用程序,它要和三个处理订单的服务器进行通信。其中一个服务器可以处理家具订单,另一个处理书籍订单,第三个则处理 DVD 订单。假定这些系统之间的唯一差别是项目的数据库,则我们可以使用相同的客户程序来访问不同的服务。唯一的差别应该是服务的端点。(这是 UDDI 所依赖的概念之一。)WSDL 的 SOAP 绑定通过使用 soap:address 元素来做到这一点。

<service name='Account' >
   
<port name='AccountSoapPort' binding='wsdlns:AccountSoapBinding' >
      
<soap:address location='http://coldrooster.com/ssf/account.asp' />
   
</port>

</service>

许多能识别 WSDL 的工具包允许在读取文件后更改端点。如果知道实现端口的其它服务的位置,或者使用 UDDI 服务器来获取类似的信息,则可以使用同一个客户程序与不同的端点进行通信。

如果花费一些时间来考虑 SOAP 接口,您的 Web 服务将可以从任何计算机上进行访问。如果多个资源提供了相同的服务,公共接口将允许单个客户程序访问不同的 Web 服务。使用自动生成 WSDL 的工具设计这些应用程序时,请花费一些时间来查看生成的 WSDL 并确保只发送绝对必要的信息。因为对于简短的操作,您会发现网络将成为瓶颈。

Web 服务的全球化

还应该考虑访问 Web 服务的类型。世界各地的用户可能都在使用该服务,这对您意味着什么呢?

您可能会遇到数据存储和传输的问题。如果您的服务接受字符串数据,请确保可以接受、传输和存储国际字母表(例如日语、西里尔语、阿拉伯语等等)。有了收藏服务,我们可以通过要求服务接受 UTF-8 和 UTF-16 编码的 XML 来处理该问题。Microsoft? SOAP Toolkit v2 可以做到这点。考虑后端系统时,您还应该考虑大字符集问题,即使用支持 Unicode 的字符串类型和数据存储器。对于收藏服务,我们使用 Visual Basic,因为它可以在本地处理 Unicode 字符串。在存储器端,我们使用 nchar 和 nvarchar 数据类型在 Microsoft? SQL Server 中存储所有的字符串。正如您可以看到的,挑选合适的工具可以更轻松地满足国际化这个方面的要求。

您还需要考虑如何分发 Web 服务文档。由于大多数开发人员可以使用英语进行读写,因此只要提供规范的英文文档,您就可以赢得巨大的访问量。在这些文档中,不要使用俚语,而应该使英文尽可能简单易懂。要吸引其它国家中的用户接受您的服务,您可能需要考虑使用开发人员的母语提供文档。

返回 SOAP 错误时,您可能需要考虑允许被授权者为 Fault.Description 成员指定首选语言。错误数适中将有助于服务的最终用户进行查看。我们曾想提供一些以特定语言表述的错误字符串,但最终决定不这样做。(我听说 Project Lucy 正在考虑对错误字符串进行翻译,可能在今年的晚些时候他们的小组将会就这一问题进行讨论。)相反,我们编写了有关文档,在其中指定了一些 Fault.Code 值,Web 服务提供的函数会返回这些值。这样将允许编程人员编写使用收藏服务来处理自己的错误和本地化的应用程序。

还应该考虑:当 Web 服务不仅在您所在地区而且在其它地区被公众接受时,应该做些什么。此时,Cold Rooster 的服务都集中在华盛顿州的雷蒙德。美国之外的用户必须通过横跨大洋的 Internet 链接来使用服务。如果这样的用户很多,我们必须考虑在世界的其它地区部署 Web 服务,以便用户获得更快的响应时间。使用全球分布的服务器来处理请求以后,又出现了新的问题。其中一个是:扩展 Web 服务时,必须考虑如何分发用户数据。

对于收藏服务,以下列举了可能出现的一些新问题:

被授权者希望能够访问所有 Web 服务器上的 Web 服务,因此我们必须提供在数据中心之间同步数据的方法。

无论最终用户处于哪个位置,他们都希望数据可以传送给他们。这意味着我们必须强制被授权者记住所联系的哪个数据中心可以获取最终用户数据,或者将所有的用户数据传播到所有数据中心。

当审计事件在全球发生时,我们如何处理审计日志?最有可能的是:在某个位置的某个数据库中存储所有的审计数据,并简单地将对该数据源的请求排队,以避免任何延迟问题。

我们必须设计规则,解决全球各地分散的数据中心之间的更改冲突。例如,被授权者可能在两个不同的服务器上更改了管理联系人数据,我们必须要提出如何处理这种冲突的规则。

与全球化有关、但易于忽略的最后一个问题是确定如何接受付款。您是允许用户使用任何货币付款呢,还是要求他们使用您的本地货币付款?全球化这方面的问题可能非常复杂,我们现在暂时将它放入思想库中,如果读者感兴趣,我们将在以后的文章中进行讨论。

总结

设计 Web 服务时,要考虑 Web 服务是如何使用的。如果在工具所生成的 WSDL 文件方面花费一些时间,可以优化它在网络上的性能。这些文件将有助于找出在何处传送大量数据会对总体吞吐量造成不良影响。同时,避免将所有功能都放置在一个存储桶中,除非这样做是非常必要的。在一个端口上将相关的功能归集在一起。

考虑全球化问题时,应尽量使设计的程序适用于处于不同地区的用户。预料到用户可能使用多种语言。对于收藏服务,我们进行了测试,确保可以为大字母表的语言(例如中文)存储字符串。测试您的 Web 服务时也应该这么做。标识可能不在前 128 个 ASCII 字符范围内的所有字符串,并确保可以存储和提取中文、阿拉伯语以及其它一些字母表的字符串。这将检查您处理 Unicode 字符串的能力。

有关 WSDL 的更多知识,请查阅以下文章:

WSDL 背景资料:

WSDL 入门(英文)

Yasser Shohoud 撰写的 WSDL 简介(英文)

相关推荐