作者:David Orchard
本文转自:http://dev2dev.bea.com.cn/techdoc/other/200408223.html
简介
设计XML的目的是创建基于自描述标记的语言。这些语言的必然演变称为版本管理。版本管理意味着添加、删除或修改语言的一些部分。使版本管理实际可用是计算中最困难的问题之一,长时间内尝试多次均失败了。Web 兴起和流行的一个可能原因,是因为演变和版本控制被内建到HTML和HTTP报头中,这两者都为理解实现其分散的扩展和版本管理的扩展提供了明确的扩展点和规则。
XML Namespaces为识别语言的各种版本提供了一个理想的机制,而所有的XML schema语言(如W3C XML Schema)规定了受控的扩展性。
本文描述了在系统间实现更有效的松散耦合的多种技术,这些技术提高了当相关系统演变时向后兼容和向前兼容变更发生的可能性。这些技术针对具有或不具有schema传播的可兼容变更而设计。许多规则被描述用于XML词汇的版本管理,利用XML Namespaces和XML Schema结构。它包括与提供扩展容器模型的语言协同工作的规则,特别是SOAP。该规则集被称为扩展性的“必须忽略(Must Ignore)”模式。奇怪的是,用于HTML标记和HTTP 报头的“必须忽略”模式明显地促进了它们的采用,但还没有被XML从业者广泛采用。本文的目标是为当前的Schema验证环境纠正这种情况。后面的材料将讨论创新的不严格的schema验证环境。
定义兼容性
FOLDOC [1]提供了向后和向前兼容性的定义。本文将重复这些定义,并重点讨论消息的动作,特别是发送方和接收方,而不是客户端和服务器。向后兼容性意思是可以推出一个新版本的接收方,不会中断现有的发送方。这意味着发送方能够向理解新版本消息的接收方发送老版本的消息,而接收方仍能够成功地处理该消息。向前兼容性意思是老版本的接收方能够处理新消息而不中断。当然老版本将不会实现任何新的行为,但发送方能够发送新版本的消息,并且仍能够成功处理它。
换句话说,向后兼容性意味着现有的发送方能够使用已经更新的服务,向前兼容性意味着较新的发送方能够继续使用现有服务。
向前兼容变更典型情况下涉及添加可选的元素和/或属性。与引入非向前或向向后兼容的变更相关的成本经常很高,典型情况下需要所部署的软件进行更新来适应新的版本。下面描述的规则优化了进行向前和向后兼容变更的情况。本文认为这意味着不变更命名空间名称或变更元素名称。
兼容性是针对单个消息的发送方和接收方来定义的。然而,大多数web服务规范提供输入和输出的定义。在兼容性的这些定义中,一个更新了其输出schema的web服务被认为是一个新发送方。当将兼容性定义应用到输出消息时,它简单地反转输入消息的发送方/接收方术语。如果一个接收方更新了输出消息的schema,那么它正在“发送”一个新版本的消息,因此它被认为是一个“发送方”。
识别和扩展语言
典型情况下,在语言中设计扩展性会导致一个更加松散耦合的系统。扩展性允许发送方变更实例而无需通过集中授权。这样,第一个与扩展性相关的规则是:
1. 允许扩展性规则:语言应设计为具有扩展性。
对扩展性的一种基本要求是能够确定元素和属性的语言。XML Namespaces[14]提供了一种将URI和XML元素或属性名称相关联的机制,以此指定名称的语言。这也能够防止名称冲突。
W3C XML Schema[15]提供了一个称为通配符(()的机制,用于控制来自某些命名空间的元素允许的位置。通配符指出指定命名空间中的元素被允许存在于通配符出现的实例文档中。这考虑到了以后schema以严格定义的方式进行扩展。扩展文档的接收方能够识别它们不理解的扩展并根据扩展的处理模型将其安全地忽略。
使用namespace属性来控制扩展元素能够来自于什么命名空间。该属性的主要值包括:##any,这意味着可以使用来自任何可能的命名空间的元素来扩展schema;##other,它只允许来自除当前命名空间之外的命名空间的扩展元素;##targetnamespace,它只允许来自当前命名空间的扩展元素。
使用processContents属性来控制XML parser如何验证扩展元素。允许的方法包括:"lax",允许验证发生;"strict",需要验证;"skip",跳过验证。本文使用"lax"验证,因为它最灵活,而且是web服务规范的典型选择。
扩展性的"Must Ignore"模式的主要目标是允许对文档的向后和向前兼容变更。最少情况下,这意味着既不修改也不添加命名空间名称,也不变更元素名称。在目标命名空间中添加元素名称只能使用##any命名空间或一个##other命名空间和词汇的目标命名空间的组合来完成。
许多例子说明了这些规则的应用。想象一下一个定购单从一个机器发送到另一个机器。定购单的处理会生成一个“装运”的消息。但这个消息在接收到定购单一段时间后才发送。发送软件必须为响应(同步消息)而等待一些时间,这很不方便。更好的模型是接收方能够自己控制发送“装运”消息,不需要等待。接收方“回调”最初的发送方,因此得出术语“回调”。发送方在回调地址中向接收方提供一个地址。这表示接收方向发送方发送任何后续消息应该使用的地址。在web服务中,这个Callback典型情况下会作为一个SOAP 报头块来发送。
我们的优先选择是使用一个##any的可扩展样式。使用该模型的回调类型如下:
示例 1 – 一个使用##any实现扩展性的schema
然而,W3C XML Schema的决定约束(后面会更详细介绍)会阻止该模型工作。当回调的后续版本添加一个可选元素时,问题就出现了。超时就是一个很好的例子。回调的超时对于接收方是一段内容丰富的信息。如果接收方不理解超时,那么它们能够继续处理。下面的schema大约是使用通配符所期望的,但是由于决定约束,它是非法的:
示例 2 – 一个非法的schema
由于该模式无法工作,为了达到最初目标,我们需要创建一个大致等效的设计模式。为了在同一个命名空间中允许新扩展,作者必须创建一个在同一个命名空间中允许扩展的扩展类型。扩展类型应当只被用于同一个命名空间中的未来兼容扩展。我们需要两个或更多规则来允许XML语言定义的适当版本管理。首先是命名空间的规则:
2. 任意命名空间规则:扩展点应该考虑任意命名空间中的扩展。对于XML Schema应用程序,扩展点应当是一个考虑了目标命名空间中的扩展的元素,一个考虑了任何其它命名空间中的扩展的通配符。
允许扩展性的规则:
3. 完整扩展性规则:所有XML元素都应该考虑元素定义后的元素扩展性,并且允许任意属性。
一个遵守这些规则的回调类型的例子:
示例 3 – 一个具有扩展性的回调类型
因为目标命名空间中的每个扩展都在扩展元素中,所以每个后续目标命名空间将增加被另一层的嵌套。尽管每个扩展的嵌套层不是所期望的,它是今天在应用严格W3C XML Schema验证时能够完成的。如果能够为一种语言完成多种可兼容的修改版本,那么拥有多个嵌套元素看来也是值得的。这种技术允许在保持目标命名空间本身验证的同时,在目标命名空间中进行扩展的验证。
总的来说,一个扩展可以由一个新规范定义,该规范对以前的规范进行标准参考,然后定义新元素。制作这样一个扩展不需要来自规范编撰者的允许。实际上,XML命名空间的主要设计点是允许分散扩展。推论是对于在同一个命名空间中的扩展,许可是必需的。命名空间有一个拥有者,修改某事物含义的非拥有者可能是有害的。
理解扩展
发送方理想情况下应该能够用新元素扩展现有的XML文档,而不需要接收方修改现有的实现。扩展是迈向这一目标的一个步骤,但达到兼容性也需要一个用于扩展的处理模型。软件在遇到扩展时的行为应该清楚。这样,我们引入下一个规则:
4. 提供处理模型规则:语言应该指定一个处理模型来用于处理扩展。
使兼容修改成为可能的最简单的处理模型是忽略不理解的内容。这个规则是:
5. 必须忽略规则:文档接收方必须忽略有效XML文档中它们无法识别的XML属性和元素。
该规则不要求以物理方式删除元素,只是为处理目的忽略它们。有许多“必须忽略”规则的著名用法。HTML 1.1、2.0和3.2遵守必须忽略规则;它们指定在分段化(tokenization)过程中将任何未知的开始标签或结束标签映射为无。HTTP 1.1[6]指定接收方应该忽略任何它不理解的报头:“无法识别的报头字段应该被接收方忽略,并且必须被透明代理转发。”XML的必须忽略规则在1997年由WebDAV工作组首次引入[19],在WebDAV规范RFC 2518 [5] section 14中标准化,后来作为Flexible XML Processing Profile单独发布[2]。
有两种与处理扩展相关的主要词汇表类型。这些类型是面向数据的,是演示(或文档)应用程序。对于面向数据的应用程序,例如web服务,规则是:
6. 必须全部忽略规则:三必须忽略规则应用于无法识别的元素和它们的后代元素。
例如,如果一个在SOAP 报头块中具有无法识别元素的消息被接收到,它们必须被忽略,除非被标记为“mustUnderstand”(参阅下面的规则10),但期望将无法识别的元素写到日志文件中是合理的。
面向文档的词汇表可能需要一个不同的规则,因为该应用程序通常需要表示未知元素的内容。针对面向文档的应用程序的规则是:
7. 必须忽略容器规则:必须忽略规则只应用于无法识别的元素
它保留元素的后代元素,例如用于显示目的的元素。
一种语言可能为处理扩展提供一种不同的模型,而不是忽略无法识别的组件。一个模型是,如果接收方发现一个无法理解的组件,它将产生一个故障。例如接收方必须理解任何扩展的一个安全规范。这需要忍受显著的缺点:它不允许在语言中发生兼容修改;修改不能被忽略。另一个模型是一个备用模型,如果接收方部理解扩展,替代元素将被提供。XSLT 2.0提供了这样一个模型。
版本管理
当需要一个新版本的语言,并且它与旧语言向后兼容时,那么作者必须为新语言中的名称决定命名空间名称。有两个选择:创建一个新命名空间或重用现有的命名空间名称。我们认为重用更加有效,我们将在“新命名空间”部分探究选项#1的问题。重用命名空间规则是:
8. 重用命名空间名称规则:如果对规格进行了向后兼容变更,那么旧的命名空间名称应该与XML的扩展性模型一起使用。
一个重要的结论是,新命名空间名称只在进行不兼容变更时才需要。
9. 新命名空间中断规则:在不允许向后兼容时使用一个新命名空间名称,如果软件不理解新语言组件,它必须中断。
非向后兼容修改典型情况下以两种方式发生:添加一个所需信息项;或变更一个现有信息项的语义。
重用命名空间规则要求遵守前面的“必须忽略”和“任意命名空间”规则。如果不遵守这些规则,那么将阻止语言设计者进行兼容变更和重用命名空间。
我们已经强调为可兼容扩展重用命名空间名称是很好的做法。相反的情况是命名空间拥有者能够通过提供允许其他命名空间的扩展点()来为兼容变更使用新命名空间。该技术有一个问题:在不同命名空间中的扩展意味着组合schema不能被完全验证。特别是,没有方法创建一个约束通配符的新schema。例如,想象ns1包含foo和bar,是不可能采用SOAP schema(一个使用通配符的schema示例)并要求ns1:foo元素必须是报头元素的子元素,ns1:bar必须不是只使用W3C XML构件的报头元素的子元素。确实,对于该功能的需求产生了一些WSDL功能。新命名空间名称方法导致不适当分解的规格和命名空间,因为相关的结构将位于分开的命名空间中。进一步讲,重用相同命名空间具有较好的工具支持。许多应用程序使用单一schema来创建等价的编程结构。这些工具经常与对“所生成”结构的单一命名空间支持一起工作得最好。命名空间名称的重用至少允许命名空间作者对命名空间进行变更,并执行对扩展的验证。
默认处理模型重写
假定采用了必须忽略规则,常有的情况是:扩展的创建者要求重写必须忽略规则而使接收方能理解扩展。
10. 提供mustUnderstand规则:容器语言应该提供一个“mustUnderstand”模型,用于处理重写默认必须忽略规则的扩展的可选性。
该规则和必须忽略规则一起工作来为扩展提供稳定的、灵活的处理模型。最简单和最灵活的重写技术一个指示项目是否必须被理解的mustUnderstand 标志。指定理解的SOAP[7]、WSDL[8]和WS-Policy[10]属性以及值分别是:
soap:mustUnderstand="1",wsdl:required="1",wsp:Usage="wsp:Required"。 SOAP可能是提供mustUnderstand模式的容器的最常见情况。默认值是0,是必须忽略规则。
一个mustUnderstand标志允许发送方将扩展插入到容器中,并使用mustUnderstand属性来重写必须忽略规则。这允许发送方在不改变扩展元素的父元素的命名空间的情况下扩展消息,保持向后兼容性。显然,接收方必须被扩展以处理新扩展,但当前在语言处理模型和扩展的处理模型之间有一个松散耦合。
有其他可能的技术,如提供一个元素来指示哪些扩展命名空间必须被理解。
在有些情况下,一种语言不提供mustUnderstand机制。在缺少mustUnderstand模型时,如果接收方不理解扩展命名空间,没有方法迫使接收方拒绝一个消息。
确定性
XML DTDs和W3C XML Schema拥有一个规则,要求schema具有确定性的内容模型。下面的例子来自XML 1.0规范。
例如,内容模型 ((b, c) | (b, d))是非确定性的,因为给出了一个初始b,XML处理器在不向前查看哪一个元素紧跟b的情况下,不知道模型中的哪一个b是匹配的。
##any的使用意味着存在一些我们可能希望表达的schema,但不被允许。
· 使用##any的通配符(这里minOccurs不等于maxOccurs),在元素声明之前不允许。该元素的一个实例将对于##any或该元素有效。可以使用##other。
· 在使用##any的通配符之前的元素必需有maxOccurs的基数,它等于其minOccurs。如果不一样,譬如minOccurs="1"和maxOccurs="2",那么可选的事件会与该元素定义或##any相匹配。作为该规则的结果,minOccurs必须大于0。
· 必须避免使用在使用##any的通配符后添加元素定义的导出类型。导出类型可能在通配符后添加元素定义,然后所添加元素定义的实例可能与通配符或导出元素定义相匹配。
11. 确定性规则:通配符的使用必须是确定性的。通配符的位置、通配符扩展的命名空间、minOccurs和maxOccurs的值是受约束的,类型限制是受控的。
如前面所示,通用设计模式将提供一个扩展性点——而不是一个元素——以允许类型末端的任何命名空间。典型情况下,这可以通过来做到。
在许多情况下确定性使此无法作为一个完整解决方案工作。首先,扩展点只能出现在原有schema中的所需元素后,将扩展性的范围限制在原有schema中。其次,向下兼容变更要求所添加的元素是可选的,这意味着minOccurs="0"。确定性防止我们将一个minOccurs="0"放置在##any的扩展点前。这样,当在一个扩展点添加一个元素时,作者能够使该元素可选,丧失扩展点,或者作者使该元素成为必需的,并丧失向后兼容性。
这为什么困难?
我们已经说明了,通过完全利用但不需要新schema定义的兼容变更,使用XML和W3C XML Schema来达到松散耦合是困难的。遵守这些扩展性规则导致W3C XML Schema文档更加笨重,同时也更缺少表现力。W3C XML Schema的扩展性处理所引入的结构限制是W3C XML Schema设计的结果,而不是基于schema的结构的固有限制。
对于W3C XML Schema来讲,如果它能够向任意位置(如其他元素前)添加元素的话,将会很有用。但是确定性约束限制了它。一个较少限制的确定性模型类型可以被使用,例如URI规范[4]中定义的“greedy”算法。这将允许在通配符前放置可选元素,而无需引入扩展类型。它仍然不允许元素前的通配符,因为通配符将匹配元素。进一步说,它不允许通配符和类型的类型扩展共存。一个“priority”通配符模型(一个元素能够被一个通配符匹配,或者可能的话一个元素能够用另一个元素匹配)将允许通配符位于元素声明前或后。另外,只允许尚未定义元素的通配符——其他命名空间有效地加上任何在目标命名空间未定义的东西——是另一个有用的模型。这些修改将也允许继承和通配符较干净地混合。但那仍然意味着作者必须在它们的类型中使用通配符。需要一个与前面提到的通配符变更结合的类型级any元素。一个可能的解决方法是序列声明有一个指定任何位置都允许扩展的属性,然后有一个指定命名空间、元素和验证规则的相当属性。
上个方法的问题是:使用特定schema,有时候必须在系统的不同部分以严格或不严格的方式应用相同的schema。对于Internet一个长期存在的规则是健壮性原理,在Internet Protocol[3]中有详细说明:“通常,一个实现必须在其发送行为上保守,在接收行为上自由。”在schema验证术语中,发送方能够以严格的方式应用一个schema,接收方以宽松的方式应用schema。在这种情况下,严格的程度不是schema的属性,而是如何使用的属性。一个看来能解决这些问题的方案是定义一种形式的schema验证,允许一种开放的内容模型,该模型在对schema进行版本管理时被使用。我们称这种模型验证为“by projection”,其忽略而不是拒绝出现在消息中而没有被schema明确定义的组件。我们计划在将来探讨这个宽松的验证模型。
关于W3C XML Schema扩展性的最后评价是:对于在保持扩展性的同时来定义用来验证已知扩展的schema方面仍然有未满足的需求。一个作者将希望创建一个基于一个扩展schema的schema,但在保持通配符扩展性的同时在特定通配符中混入了其他已知的schema。我们在一些领域(像描述SOAP 报头块)遇到了这种困难。从多个schema组合schema的主题比较困难,但也很迫切。
离开通配符扩展性的主题,如果接收方不理解扩展类型,就像在xsi:basetype=""中,那么如果实例文档能够表明一个基础类型,在Web上使用扩展类型可能会更加令人愉快。如果接收方不理解基础类型的扩展,那么接收方能够后退来使用基础类型。
另一个对于架构的改进的领域是XML(或者甚至是W3C XML Schema)可能已经提供了一个mustUnderstand模型。在目前情况下,每个提供mustUnderstand模型的词汇表重新改造了mU wheel。XML可以提供一个每种语言都能使用的xml:mustUnderstand属性和模型。2000年2月[18],Tim Berners-Lee在他的关于强制扩展的设计笔记上详细描述了XML中对其的需要,但是XML 1.0和1.1都没有包括该模型。
最后,在对于W3C XML Schema实现的符合性测试方面存在不明确性。W3C XML Schema测试集[16]不测试一些已经在这里排除的、比较通用的情况。例如,重写不同风格的通配符测试,它是一个复杂类型中的xs:any。它们不包括一些非确定性情况,这些情况典型地可以通过组合minOccurs/maxOccurs变化和##any、或者组合继承与##any来获得。这样,一些实现无法正确地对非确定性进行测试,这可能产生无法互操作的文档。
人们共同关注的问题是对这些功能和组合的实现支持。这些示例已经在许多不同的schema分析器和工具包上进行过试用,如XML Beans、SQC和JAX-RPC。尽管不可能知道是否所有的实现都支持这些规则,但好像那些已经测试过的实现支持很好。作者一定对了解不支持这些规则的工具包感兴趣。
结束语
W3C TAG决定版本控制和扩展性主题对于web架构足够重要,因此在一个发现上付出工作[20],并且将材料包含进Web Architecture文档[21]中。尽管本文为TAG材料提供了一个起始点,但该材料将包含更广泛的范围,并且比一篇文章在交互和重复的方式方面更加进了一步。对于正在进行的扩展性和版本控制领域的处理,读者可以遵循TAG材料。
本文描述了一些在语言构造和扩展中使用XML、W3C XML Schema和ML Namespaces的规则。这套规则的主要目标是允许语言设计者能够为了获得系统间的松散耦合而对其语言进行向后和向前兼容的变更。
一定程度上,这里介绍的技术将##any和##other设计和周知的规则组合起来,以生成一个可实现兼容扩展性和使用W3C XML Schema的验证的版本控制目标的设计。命名空间名称的拥有者能够向扩展元素中添加向后和向前兼容变更,同时保持验证所有组件的能力,其他作者能够在##other通配符位置添加他们的修改。
参考
· Free Online Dictionary of Computing
· Flexible XML Processing Profile
· IETF RFC 791
· IETF RFC 2396
· IETF RFC 2518
· IETF RFC 2616
· SOAP 1.1
· WSDL 1.1
· WS-Callback
· WS-Policy Framework
· Xfront's Schema Best Practices
· W3C Note, Web Architecture: Extensible Languages
· W3C XML 1.0
· W3C XML Namespaces
· W3C XML Schema Part 1
· W3C XML Schema Working Group's Test collection for Any
· XML.com: W3C XML Schema design Patterns,作者:Dare Obasanjo
· Tim Berners-Lee关于演化、可扩展性和Must Understand的文章:
o http://www.w3.org/DesignIssues/Mandatory.html
o http://www.w3.org/DesignIssues/Extensible.html
o http://www.w3.org/DesignIssues/Evolution.html
· http://lists.w3.org/Archives/Public/w3c-dist-auth/1997AprJun/0190.html
· W3C TAG Finding on extensibility and versioning
· W3C TAG Web Architecture document section on extensibility and versioning
致谢
作者感谢许多对本文做出贡献的评论者,特别是David Bau、William Cox、Edd Dumbill、Chris Ferris、Yaron Goland、Hal Lockhart、Mark Nottingham、Jeffrey Schlimmer、Cliff Schmidt和Norman Walsh。征得作者的允许,本文借用了WS-Calback中的例子和一些文本[9]。