10 月 23 - 25 日,QCon 上海站即将召开,现在购票,享9折优惠 了解详情
写点什么

Web Services 模式——第一部分:基本数据类型

  • 2009-03-25
  • 本文字数:4901 字

    阅读完需:约 16 分钟

XML 消息交换是大多数 web 服务的基础,包括 SOAP 和 REST 方式。使用 XML 导致了一些缺陷,包括性能的潜在问题,但是这也提供了抽象层,允许参与交换各方之间的松耦合。为了使松耦合真的起作用,你需要定义交换中的 XML 文档的结构,便于验证文档的正确性。W3C 的 XML Schema 定义语言(在本文后面部分简写为“模式”)是用于消息结构定义的最常用的方法。

大多数 web 服务不与 XML 文档直接交互,而是通过 web 服务工具包的数据绑定转换层。这方便了应用开发人员,因为这意味着他们可以直接使用选定编程语言的数据结构。但是数据绑定过程需要处理模式数据类型、结构和编程语言数据类型、结构之间的不匹配问题,这些都会为应用制造麻烦。如果你想要你的 web 服务提供一致的、平台兼容性(这也是使用 web 服务的首要意义所在),你需要设计模式定义以避免潜在的问题——或者至少意识到使用存在问题的模式的风险。

在本系列文章中,我们将探讨一下来自于模式和 web 服务数据绑定之间不匹配问题的各个方面。在第一篇文章中,我们从最基础的角度开始,看一看简单数据类型和相关的问题。

数字表示

数字是业务数据中最基本的类型。既然数字如此重要,你可能会认为模式在这一领域运转的很稳定和一致。从某种抽象意义上来说,的确是这样——但是当 web 服务工具包应用模式时,你仍然会遇到多种问题。

其中一个问题是内建模式数字类型的多样性。图 1 展示了本领域模式数据类型的组成部分。为了便于理解,运用特殊化方法——这棵树从上到下的分支走得越远,数据类型就越特殊化。在顶层,是 anySimpleType 类型,其下面是基本的数据类型 float、decimal 和 double。Float 和 double 是最终类型,符合 IEEE 对浮点书的标准定义,也提供了跨 web 服务的良好互操作性:每一个主流编程语言都支持 32 位浮点数字,匹配浮点数规范,支持 64 位浮点数字,匹配 double 模式规范,因此,web 服务工具包可以简单的把这些类型映射到原生语言类型。可能在特殊值(非数字、正无穷、负无穷、正零、负零)的编程语言文字表达上和模式之间有细微的区别,不过工具包可以很容易的处理转换。

图 1. 模式数字类型

当你来到 decimal 分支的时候,你就会遇到问题。Decimal 本身被定义为一个任意长度小数位数的字符串,包括可选的正负号和小数点。Integer 是 decimal 的直接子节点,表示 decimal 的一个子集,包含可选的正负号,但不允许小数点。Integer 的子节点做了进一步约束,nonPositiveInteger 和 nonNegativeInteger 限制了其值大于零或者小于零,long 则把取值范围限制在 64 位 2 进制补码表示。Int、short 和 byte 进一步限制了范围,分别是 32 位、16 位和 8 位 2 进制补码表示,而 unsigned 各种变形则是同样位数的非负值。

所有主流编程语言支持匹配 long、int 和 short 模式类型的值,但是其他变形则有可能出现问题。举例来说,Java 不包含对应 unsignedLong 和 unsignedInt 的内建数据类型。Java web 服务框架一般使用特殊的类而不是原生类型来应付这种语言的缺失,但是这使得 web 服务接口多少有些笨拙,同时可能引起性能问题(因为计算时内建类型一般远远快于对象类型)。

甚至 decimal 和 integer 也会引起问题。大多数 Java 工具包使用标准的 java.lang.BigDecimal 和 java.lang.BigInteger 类来处理这些类型,导致了糟糕的性能,但是支持无大小限制的值。.Net 则使用 128 位固定长度的表示法,限制了值域(模式规范所许可的),但是提供了相对较好的性能。

模式数字类型令人困惑和不协调(比如,为什么存在 nonPositiveInteger 而没有 nonPositiveDecimal 类型?),一般只是用于某种语法上的便利(因为范围可以通过 simpleType 限制实现)。鉴于此,在你的模式定义中,最好避免使用大多数类型,特别是那些用于 web 服务的。尽量使用某些特定的类型(double 和 float 用于实数,long 和 int 用于整数),因为这些与编程语言的原生类型保持一致。如果你需要使用超过这些类型范围或者精度的值,请记住 decimal 和 integer 由于依赖实现可能无法提供你所想要的,你可以考虑使用字符串,在应用程序代码中处理这种转换。

时间问题

时间相关的值是使用模式的另一个常见问题来源。模式定义了九种独立的时间相关的数据类型,全部基于 Western Gregorian 日历形式。不像数字值,时间相关的类型彼此之间不是特殊化的关系——相反,它们都是直接继承自通用类型 anySimpleType。

应用最广泛的时间类型是 dateTime、date 和 time。这三种数据类型共享同一种表示形式,dateTime 最完整。下面是一个 dateTime 值的例子,我写这篇文章的当前时间是:“2008-09-08T15:38:53”。Date 使用相同的表示法,只是去掉了‘T’和后面的小时——分钟——秒(在本例中,剩下"2008-09-08"),time 值则去掉了“T”之前的所有东西,只保留小时——分钟——秒(“15:38:53”)。

到目前为止很简单,是吧?让人感到困难的地方在于这些值的实际解释。日期和时间根据你所在的地点而变,也就是时区。举例来说,当我在新英格兰州写这篇文章时,比格林尼治时间提前 12 小时,比太平洋西部时间(美国西海岸所用时区)提前 19 小时。当前时间的 dateTime 值是"2008-09-08T15:38:53",西雅图时间则是"2008-09-07T20:38:53"。

很多程序需要指定日期和时间,允许把一个值关联到另一个。模式通过允许使用附加时区提示的手段满足这种需求。这种时区提示既可以使用字母‘Z’表明是格林尼治时间,也可以使用相对格林尼治时间的偏移量。因此,所有 dateTime 值(和很多其他变形)都可以用于指示同一时间:“2008-09-08T15:38:53+12:00”、"2008-09-07T20:38:53-08:00"或者 “2008-09-08T03:38:53Z”。

但是模式并不强制你指定时区提示,没有这种限制,日期 / 时间只能被解释为世界上任意地区的准确时间。对于某些程序,这种性质可能恰好是你想要的——举例来说,某人的生日日期通常被视为一个特定的日子而不管是哪个地区,同样的,人们以世界各地的当地时间为准庆祝罗马制新年——但是对于其他程序,就会产生大麻烦。考虑电话会议的例子,所有参与各方都需要调整会议时间到本地时间。

不幸的是,模式不允许你区分何时需要完全指定的日期 / 时间、何时需要无分区的值(至少不是通过 web 服务工具包解释的方式——你可以使用 simpleType 限制,但通常会被工具包忽略)。因此模式在这一点上的含糊不清意味着工具包需要处理存在和不存在时区提示的两种值。

这种处理两种值的需求在解释阶段让人很头痛,特别是因为编程语言一般基于绝对时间值实现 date/time 类型。没有办法把缺少时区提示的模式值正确的转化为绝对时间。当然,这无法阻止工具包处理类似值。在大多数情况下,他们把这些值当作本地时区值转化,这通常也是你所期望的——但是,当它不是时,由此导致的问题很难定位。

由于时区造成的问题对于 date 类型特别麻烦。多数情况下,人们认为日期是日历上的一个固定刻度。举例来说,当你签署法律文件时,你一般会写上日期。如果你同意一个新项目,一般会有一个计划完成日期(通常都是空想)。买东西时有时需要出示驾照表明年龄,店员会查看你的生日日期,并与年龄限制作比较。在所有这些情况下,日期被认为具有日期分辨功能,时区之间的差别被忽略了。但是,模式日期类型使用相应的时区提示,如 dateTime 和 time 类型。这种做法分割了模式日期和常用日期。一般来说,这会通过把日期转化为 00:00(午夜,一天的开始时刻)时间表示法来解决,不管是什么时区。但是如果你把日期值使用本地时区打印出来,你可能会发现它与文档里最初指定的值不同。

如果模式分别定义了带有时区提示和不带时区提示的日期\时间类型,应用程序就会易于挑选其所需的。否则,工具包难以处理一个模式上存在缺陷的日期 / 时间表示法。Java 的 JAXB 2.0 采用了可能是最综合的办法来处理这个问题,通过一个特殊的类(javax.xml.datatype.XmlGregorianCalendar)来处理所有模式日期 / 时间类型。这种方法保存了表示法的所有细节,只是把解释过程交给了开发人员。其他工具包一般使用缺省办法,例如假定是本地时区。

既然有这么难解的问题,最好的处理办法是只为那些完全指定时区提示的值使用日期 / 时间类型,并保证生成的所有文档都包含时区提示。大多数 web 服务工具包会自动为你生成时区提示,所以最后一步很简单。要求输入文档也使用时区提示则困难些,特别是因为文档会经过多步处理。如果你想要不陷入错误的转化假定所引起的问题中,最好的解决办法是在模式表达中使用字符串类型,这样的话,web 服务工具包会把值传递给你的应用程序代码,而不会解释其值。

如果你需要无时区的日期 / 时间值(如生日日期),最好也使用字符串类型。从提供一个精确表示的角度看,这不是很令人满意,但是避免了 web 服务工具包把无分区的值解释成本地时区。

引用

应用程序内部使用的数据结构经常包含组件之间的多个连接,包括交叉引用和间接关联。另一方面,XML 天生是树形结构的。在 XML 中,通过包含可以很容易的表示一对多的关系,但是表示其他关系则困难。甚至是一对多关系也效率低下。考虑一个文档列举了顾客的订单历史,每一订单都有对应的付款和发送地址,但是这些地址通常是重复的。如果你对每一份订单都插入地址,则最终文档里会有大量冗余信息。

引用可以用于解决 XML 树形结构的限制。引用的思想是在 XML 文档中定义事物一次,包含一个唯一标识码。每当其他数据想要使用该定义时,使用标识码创建一个引用。

模式直接支持两种引用。第一,使用 ID 类型,定义元素标识符,可以使用 IDREF 或者 IDREFS 类型从文档中任意处关联。ID/IDREF 关联的优点是非常简单——标识符就是名字,任何类型的元素都可以在模式中定义 ID 值。缺点是使用全局上下文,因此没办法说某个 IDREF 使用的值肯定采用特定元素类型定义,用作 ID 值的名字在文档中必须唯一(即使是跨类型的元素)。一些 web 服务工具包支持使用 ID/IDREF 关联来表示数据结构内部的引用(包括 JAX-WS/JAXB 2.0,Apache Axis2 使用 JiBX 数据绑定时),其他工具包(如.Net、Axis2 使用 ADB)则不是,把 IDREF 值简单的当作文本字符串。

模式支持的第二种引用是 key/keyref 关联。ID/IDREF 关联使用数据类型定义,key/keyref 关联则是模式定义的结构组成部分。这允许 key/keyref 关联比 ID/IDREF 更具有表现力,包括定义 key 值唯一的上下文环境。但是,因为 key/keyref 关联的设计意图更倾向于文档验证而不是结构化,它们太复杂,数据绑定框架一般也不会用于做 XML 数据和数据结构之间的转化。

因此,如果你想在 XML 文档中插入关联并由 web 服务工具包处理,唯一的办法就是 ID/IDREF。一些工具包现在直接支持这种关联,其他的则只是把标识符看做字符串,但是你可以编写应用程序代码来交叉引用标识符和值、构建自己的关联。

结论

在本文中,我们探讨了在 web 服务中使用最常用数据类型时会遇到的问题。除了本文提到的还有很多其他模式数据类型(一共 42 种!),其中一些会引起其它问题。总的来说,使用 web 服务模式定义的最好办法是避免使用过度特殊化的类型(除了那些匹配常用编程语言类型的数字类型),当你想完全控制这些值时,你可以使用字符串类型转换这些值。

值得一提的是,虽然本文中讨论的一些问题可以通过数据绑定框架处理的更好一些,但是很多问题来源于模式本身。特别是,数据 / 时间类型难以使用,甚至可能因为缺少时区和非时区之间的区别来导致错误。虽然可以像 JAXB 那样使用 XmlGregorianCalendar 类把这种混乱交给用户处理,但这实在不是一种解决办法。

关于作者

Dennis Sosnoski 是一名基于 Java 的 SOA 和 web 服务领域的顾问和培训讲师。他在软件开发领域工作超过 30 年,近 10 年专注于服务器端的 XML 和 Java 技术。Dennis 是开源项目 JiBX XML 和相关 JiBX/WS 框架的主要开发人员,也是 Apache Axis2 web 服务框架的开发者。同时,他也是 JAX-WS 2.0 和 JAXB 2.0 的专家组成员。有关他的培训和咨询服务请登陆网站 http://www.sosnoski.co.nz


给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2009-03-25 18:006438
用户头像

发布了 501 篇内容, 共 277.6 次阅读, 收获喜欢 63 次。

关注

评论

发布
暂无评论
发现更多内容

快速开发协同办公OA系统 让企业管理提质增效

力软低代码开发平台

服务超80家金融行业头部企业,腾讯会议将支持混合云部署

科技热闻

腾讯云NoSQL数据库产品2022再迎升级,多项技术细节首次公开

科技热闻

5.外包学生管理系统实战

程序员小张

「架构实战营」

空间节省50%,时序性能提升5倍,三一重工从Hadoop+Spark到MatrixDB架构变迁实现One for ALL

YMatrix 超融合数据库

三一重工 超融合数据库 数据库· YMatrix

1-5-10 快恢在数字化安全生产平台 DPS 中的设计与落地

阿里巴巴云原生

阿里云 云原生 数字化安全生产平台

iOS 15 TableView willDisplayCell获取失败

刿刀

UITableView iOS16

如何在Ubuntu20.04上安装RDP远程

吴脑的键客

ubuntu DevOps RDP

Istio的使用场景

穿过生命散发芬芳

istio 12月月更

使用 JS 转换数据的最佳实践

夏木

typescript data-convert

Wallys//QCN9074/QCN9024/WiFi6/WiFi6E/4x4 MU MIMO Dual Band WiFi Module MiniPCIe/industrial wifi6 moudle

wallysSK

QCN9074 QCN9024 QCN9072

NFTScan 与 Merlin Protocol 达成战略合作伙伴,双方将在 NFT 数据层面展开深度合作

NFT Research

NFT 数据基础设施

腾讯智慧农业首次亮相,助力青海大通农产品走进大湾区

科技热闻

教你用JavaScript实现粘性导航

小院里的霍大侠

JavaScript 编程开发 初学者 入门实战

MySQL索引的底层数据结构原理剖析(二叉树、 红黑树、Hash、B-Tree、B+Tree)

C++后台开发

MySQL 数据结构 后端开发 底层原理 C++开发

Lattice – 基于扩展点的多维度业务定制叠加

原力在线

架构 lattice 高可扩展

Flask上手:step by step

无人之路

flask web开发 Web应用开发 Python. python web

API网关与南北向安全设计

阿泽🧸

API网关 12月月更

AI 作画领域中的“神笔马良”是怎样炼成的?

行者AI

声网王浩宇:RTE 场景下的 Serverless 架构挑战【RTE 2022】

声网

架构 实时互动

TitanIDE引领企业开发工具变革

行云创新

ide CloudIDE WebIDE

互联网都在说降本增效,小红书技术团队是怎么做的?

小红书技术REDtech

量化合约对冲交易机器人app系统开发源代码部署

开发微hkkf5566

一图读懂《2022 年中国政企数智办公平台行业研究报告》

融云 RongCloud

办公 数智化 图论

国产智能BI产品崛起,帆软Fine BI、瓴羊Quick BI等应该如何选择

小偏执o

NTFS读写工具Tuxera for Mac2023下载及功能介绍

茶色酒

Tuxera2022 Tuxera NTFS2022 Tuxera NTFS Mac2022

内部CRM和商业化SAAS CRM的区别

久歌

SaaS 架构设计 CRM

超聚变服务器操作系统FusionOS与阿里云PolarDB数据库完成兼容性认证

阿里云数据库开源

阿里云 开源数据库 polarDB PolarDB-X PolarDB for PostgreSQL

数据治理:指标体系管理

用友BIP

爱奇艺:基于龙蜥与 Koordinator 在离线混部的实践解析 | 龙蜥技术

OpenAnolis小助手

开源 cpu 爱奇艺 混部 龙蜥操作系统

2022腾讯Techo前沿技术论坛召开,六位科学家分享前沿科学成果

科技热闻

Web Services模式——第一部分:基本数据类型_Java_Dennis Sosnoski_InfoQ精选文章