WCF 契约的版本管理

本文所指的契约是指 Service Contract (服务契约) 、Data Contract (数据契约)和 Message Contract (消息契约)。

这个世界唯一不变的就是 “变化” 本身。


什么是版本?

版本类似一个标记,用于标识某一特定时间点一个对象所处的状态及其所包含的行为。比如 7.9 版本的 QQ 不管装在谁的机器上,理论上其所提供的功能都是一样的。


为什么会有新的版本?

当对旧的版本进行修改,就会演变成一个新的版本。比如修改一些 bug,改进一下界面,增加一些功能等一切改动都会导致和旧版本的不一致,因此产生了一个新的版本。


为什么需要进行版本管理?

简单来说是为了保证服务的使用者和提供者说得是同一回事情,避免因为版本不一致等问题造成沟通失败。

假设,一个天气服务提供了 GetTodayTemperature、GetTomorrowTemperature 的操作用于提供今天和明天的温度。当服务发布后,就先后有很多用户通过调用该操作来获取天气信息。突然有一天,服务提供者觉得写两个操作有太多冗余代码了,于是删除了原来的两个操作,创建了一个新的操作 GetTemperature(date),通过传入的日期来获取那一天的温度。虽然服务升级了,但是很多客户觉得之前用得挺好,不想花费成本进行升级,于是继续调用原来的操作。结果显而易见,用户一定会很不开心。


通过不同的版本管理方式,可以减少甚至避免上述现象的发生。


如何进行版本管理?

通过清晰明确的版本号对版本进行跟踪。

常见的版本号由4部分组成:主版本号.子版本号[.修正版本号.编译版本号]。一般对代码没有任何影响(比如一些格式上的修改,调整等)的改动,增加的是修正版本号;如果改动是向后兼容的,则修改的是子版本号;如果不是向后兼容的,则修改的主版本号。


Web Service 中版本号表达的两种方式

1、利用 "属性"

XSD: <xsd:schema version="1.0">...</xsd:schema>

WSDL: <wsdl:documentation>Version 1.0</wsdl:documentation>,由于没有内置的 version 属性,所以通过 documentation 元素代替,这里有一定的使用规则:版本号和 Version 中间用一个空格分隔。


2、利用 "namespace"

<item xmlns="http://chenxu.me/schema/v1">...</item> ,即在 namespace 最后以 “v"+版本号的形式出现。

除处之外,目前用得更多的是使用日期的方式,xmlns="http://chenxu.me/schema/2015/12/31">...</item>


    在实际使用过程中,通常是两者结合起来使用。

两种类型的改动

前面说了,改动会导致版本的变化,而不同的改动的影响是不一样的:


向后兼容的改动

这类改动,不会对现有的客户造成影响,比如在服务契约中增加一个新的操作,对于现有的客户不会发觉任何问题。此类改动被认为是 “安全” 的。

不后前兼容的改动

与上述刚好相反,这类改动会导致现有客户的运行失败,客户只有更新到新版本才能正常运行。比如从服务契约中删掉了一个方法。这类改动往往认为是 “不安全” 的。



版本管理的三种方式

为了让大家更好的理解,这里先提供一个基础的 WSDL 版本:

<definitions name="ClientService"
        targetNamespace="http
://chenxu.me/contract/client/v1"
        xmlns="http://schemas.xmlsoap.org/wsdl/"
        xmlns:tns="http://chenxu.me/contract/client/v1"
        xmlns:myschema="http://chenxu.me/schema/client/v1">
     <documentation>Version 1.0</documentation>
     <types>
        <xsd:schema>
            <xsd:import namespace="http://chenxu.me/schema/client/v1" schemaLocation="sample.xsd"/>
        </xsd:schema>
     </types>

 <message name="GetClientRequest"> 
        <part name="GetClientRequest" element="myschema:GetClientRequest"/>
     </message>
     <message name="GetClientResponse"> 
        <part name="GetClientResponse" element="myschema:GetClientResponse"/>
     </message>
     <portType name="IClientService">
        <operation name="GetClient">
            <input message="tns:GetClientRequest"/>
            <output message="tns:GetClientResponse" />
        </operation>
     </portType>
    </definitions>


严格 (Strict)

无论是哪种类型的改动,都会导致生成一份全新版本的契约(通过 namespace 的方式递增版本号)。可以认为老版本的所有东西都已经冻结了,不准改变。一切改动(不管是)都先复制一份原来的版本,并在此基础上进行对应的修改,同时。

因为保持原版本的不动,所以此方式最终会产生一个全新的 WSDL/XSD,适用于 DataContract 和 ServicContract,且任何类型的修改均可使用。


优 点

最简单,也不用担心破坏老版本(可以随时使用老版本)。同时也是最安全的,因为会强制客户端升级到新版本,所以客户端肯定和服务端是一致的版本。


缺 点

全新的契约意味着它不支持向后兼容,为了允许老版本的客户迁移到新版本,将需要继续支持老版本的服务一段时间。也就意味着在这段期间需要同时维护两个 endpoint,这也造成维护成本的增加。


实 施

1、保持原 XSD/WSDL 不变,复制一份。

2、递增版本号:

    2.1 如果 XSD 改了,则递增 XSD 中 namespace 的版本号

    2.2 不管 WSDL 本身有没有改,均递增 WSDL namespace 中的版本号

    2.3 不管 WSDL 本身有没有改,均递增 WSDL documentation 的版本号


3、可选,如果新老版本改动不多,可以考虑让服务同时实现这两个契约接口。


演 示

增加一个 operation,尽管这是一个向后兼容的改动,仍然需要修改 namespace。

<definitions name="ClientService" 
        targetNamespace="
http://chenxu.me/contract/client/v2"
        xmlns="http://schemas.xmlsoap.org/wsdl/"
        xmlns:tns="http://chenxu.me/contract/client/v2"
        xmlns:myschema="http://chenxu.me/schema/client/v2">
     <documentation>Version 2.0</documentation>
     <types>
        <xsd:schema>
            <xsd:import namespace="http://chenxu.me/schema/client/v2" schemaLocation="sample.xsd"/>
        </xsd:schema>
     </types>
     <message name="GetClientRequest"> 
        <part name="GetClientRequest" element="myschema:GetClientRequest"/>
     </message>
     <message name="GetClientResponse"> 
        <part name="GetClientResponse" element="myschema:GetClientResponse"/>
     </message>

 <message name="AddClientRequest"> 
        <part name="AddClientRequest" element="myschema:AddClientRequest"/>
     </message>
     <message name="AddClientResponse"> 
        <part name="AddClientResponse" element="myschema:AddClientResponse"/>
     </message>

 <portType name="IClientService">
        <operation name="GetClient">
            <input message="tns:GetClientRequest"/>
            <output message="tns:GetClientResponse"/>
        </operation>

    <operation name="AddClient">
            <input message="tns:AddClientRequest"/>
            <output message="tns:AddClientResponse"/>
        </operation>

 </portType>
    </definitions>


宽松 (Lex 或 Agile)

关于该方式,MSDN 有两种叫法,即 Lex 和 Agile。此方式适用于 DataContract 和 ServicContract。

上面我们了解了严格的方式,知道它对客户端而言影响很大,而在平常使用中,我们更希望尽量小的影响客户端。宽松的方式,即对于向后兼容的改动使用向后兼容的版本管理方式(即,通过属性的方式管理版本号),而不向后兼容的改动,则使用 namespace 的方式管理版本号。


适合的改动

这类改动往往被认为是安全的,也就是说老的客户端发的请求可以被正确的处理,具体的改动包含如下:


DataContract

针对 DataContract 的大部分修改都是兼容的,请参考文章《WCF 契约之 DataContract》。

如果使用了 Schema 的验证,则只有如下的修改是支持的,其它情况都会导致 XSD 验证失败:

> 添加一个非必需的 DataMember


ServiceContract

> 添加一个新的 operation

> 给 operation 添加一个新的 参数

> 删除 operation 的一个参数 (客户端传递的参数将被丢弃,可能造成数据损失)


优 点

对于向后兼容的改动仍然是向后兼容的,最大程度的避免了影响客户端的使用。可以用同一个 Endpoint 同时支持新版本和老版本。而对于 WCF 而言,它原生支持这类的向后兼容,因此我们可以直接在代码上进行修改,而不用担心版本管理的事情。


缺 点

一旦改动发生,老版本即不复存在,无法再单独使用老版本,且无法对请求进行跟踪,无法知道请求是来自于老客户还是新客户。


实 施

1,递增版本号,只改动 WSDL documentation 的版本。

2,通过图示介绍下最终的代码结构及终结点的关系:


演 示

增加一个新的 operation,这是一个向后兼容的改动,所以不需要修改 namespace。

<definitions name="ClientService" 
        targetNamespace="
http://chenxu.me/contract/client/v1"
        xmlns="http://schemas.xmlsoap.org/wsdl/"
        xmlns:tns="http://chenxu.me/contract/client/v1"
        xmlns:myschema="http://chenxu.me/schema/client/v1">
     <documentation>Version 1.1</documentation>

     <types>
        <xsd:schema>
            <xsd:import namespace="http://chenxu.me/schema/client/v1" schemaLocation="sample.xsd"/>
        </xsd:schema>
     </types>
     <message name="GetClientRequest"> 
        <part name="GetClientRequest" element="myschema:GetClientRequest"/>
     </message>
     <message name="GetClientResponse"> 
        <part name="GetClientResponse" element="myschema:GetClientResponse"/>
     </message>

 <message name="AddClientRequest"> 
        <part name="AddClientRequest" element="myschema:AddClientRequest"/>
     </message>
     <message name="AddClientResponse"> 
        <part name="AddClientResponse" element="myschema:AddClientResponse"/>
     </message>

 <portType name="IClientService">
        <operation name="GetClient">
            <input message="tns:GetClientRequest"/>
            <output message="tns:GetClientResponse"/>
        </operation>

    <operation name="AddClient">
            <input message="tns:AddClientRequest"/>
            <output message="tns:AddClientResponse"/>
        </operation>

 </portType>
    </definitions>


半严格 (Semi-Strict)

从 Message First 的角度考虑,该方式仅仅是上述两种方式的结合体。即,根据项目组的政策,对部分修改采用 “宽松” 方式,而另一部分修改则采用 “严格” 的方式。

而从 Service 的角度考虑,使用 “宽松” 的方式,由于代码上并没有体现版本,而且使用的又是同一个 endpoint,根本无法知道发过来的请求是来自老的客户端还是新的客户端。而使用 “严格” 的方式,改动又太大,动不动就要重写一个服务契约。Semi-Strict 的方式便应运而生,它即没有 “严格” 的方式改动来得大,又能跟踪请求的来源是新客户还是老客户。不过该方式只适用于在 ServiceContract 中新增 operation 的修改


实 施

保持旧的契约不变,定义一个新的 Service Contract,并使之继承自旧版本的契约,然后在新的契约上增加新方法。因为是通过继承的方式进行版本管理,所以就算终节点只是暴露新版本的服务契约,WCF 仍然支持向后兼容,即,老版本的客户端仍然不需要进行修改。



上述通过暴露一个 endpoint,同时支持新老版本,但是这种方式不利于跟踪。另一种方式,就是使用两个 endpoint,分别指向不同的契约。

移除的注意事项

当计划从 WSDL 中移除一个已有的 operation 时,请利用 documentation 元素加上注释,如下:

<portType name="IClientService">
        <documentation>
            GetClient is scheduled for terminatation on 01/01/2016
        </documentation>

        <operation name="GetClient">
            <input message="tns:GetClientRequest"/>
            <output message="tns:GetClientResponse"/>
        </operation>
     </portType>


当已经从 WSDL 中移除了一个 operation 时,请使用注释的方式留下标记,以便日后跟踪。

<portType name="IClientService">
        <!-- AddClient Removed 31/12/2015 -->
        <operation name="GetClient">
            <input message="tns:GetClientRequest"/>
            <output message="tns:GetClientResponse"/>
        </operation>
     </portType>


我的建议

不管是哪种类型的版本控制,都请用书面的形式对每一次改动做好记录,其中必须包括:修改日期、修改的内容、受影响的 XSD、受影响的 Service。

如果是安全的改动,则使用 “宽松” 的方式。否则使用 “严格” 的方式。

参考资料

WCF Backwards Compatibility and Versioning Strategies

Best Practices: Data Contract Versioning

Service Versioning

Web Service Contract Versioning Fundamentals

Versioning in SOA

<Web Servcie Contract Design and Versioning for SOA>

文章索引

[隐 藏]

本站采用知识共享署名 3.0 中国大陆许可协议进行许可。 ©2014 Charlie Box | 关于本站 | 浙ICP备13014059号