代码之家  ›  专栏  ›  技术社区  ›  Eran Medan

您的WebService版本控制最佳实践是什么?

  •  18
  • Eran Medan  · 技术社区  · 14 年前

    我们有两个独立的产品需要通过Web服务相互通信。 支持API版本化的最佳实践是什么?

    我有 this article 从2004年开始,声称没有实际标准,只有最佳实践。有更好的解决方案吗?如何解决WS版本控制?

    问题描述

    系统A

    顾客

    class SystemAClient{
        SystemBServiceStub systemB;
        public void consumeFromB(){
            SystemBObject bObject = systemB.getSomethingFromB(new SomethingFromBRequest("someKey"));
    
        }
    }
    

    服务

    class SystemAService{
        public SystemAObject getSomethingFromA(SomethingFromARequest req){
            return new SystemAObjectFactory.getObject(req);
        }
    }
    

    可转让物

    版本1

    class SystemAObject{
         Integer id;
         String name;
         ... // getters and setters etc;
    }
    

    版本2

    class SystemAObject{
         Long id;
         String name;
         String description;
         ... // getters and setters etc;
    }
    

    请求对象

    版本1

    class SomethingFromARequest {
         Integer requestedId;
         ... // getters and setters etc;
    
    }
    

    版本2

    class SomethingFromARequest {
         Long requestedId;
         ... // getters and setters etc;
    
    }
    

    系统B

    顾客

    class SystemBClient{
        SystemAServiceStub systemA;
        public void consumeFromA(){
            SystemAObject aObject = systemA.getSomethingFromA(new SomethingFromARequest(1));
            aObject.getDescription() // fail point
            // do something with it...
        }
    }
    

    服务

    class SystemBService{
        public SystemBObject getSomethingFromB(SomethingFromBRequest req){
            return new SystemBObjectFactory.getObject(req);
        }
    }
    

    可转让物

    版本1

    class SystemBObject{
         String key;
         Integer year;
         Integer month;
         Integer day;
    
         ... // getters and setters etc;
    }
    

    版本2

    class SystemBObject{
         String key;
         BDate date;
         ... // getters and setters etc;
    }
    
    class BDate{
         Integer year;
         Integer month;
         Integer day;
         ... // getters and setters etc;
    
    }
    

    请求对象

    版本1

    class SomethingFromBRequest {
         String key;
         ... // getters and setters etc;
    }
    

    版本2

    class SomethingFromBRequest {
         String key;
         BDate afterDate;
         BDate beforeDate;
         ... // getters and setters etc;
    }
    

    失败场景

    如果A 系统A 客户机 属于 版本1 呼叫 系统B 服务 属于 版本2 它可能失败:

    • 上缺少方法 SystemBObject ( getYear() , getMonth() , getDay() )
    • 未知类型 BDate

    如果A 系统A 客户机 属于 版本2 调用A 系统B 服务 属于 版本1 它可能失败:

    • 未知类型 贝特 SomethingFromBRequest (客户端使用B版本1无法识别的更新的B请求对象)
    • 如果系统客户端足够智能,可以使用请求对象的版本1,则在 系统对象 对象(对象) getDate() )

    如果A 系统B 客户机 属于 版本1 调用A 系统A 服务 属于 版本2 它可能失败:

    • 类型不匹配或溢出 SystemAObject (返回) Long 但期望 Integer )

    如果A 系统B 客户机 属于 版本2 调用A 系统A 服务 属于 版本1 它可能失败:

    • 类型不匹配或溢出 SystemARequest (请求 而不是 整数 )
    • 如果请求以某种方式通过,则强制转换问题(存根是 但服务返回 整数 在所有的WS实现中不完全兼容)

    可能的解决方案

    1. 升级版本时使用数字:例如 SystemAObject1 , SystemBRequest2 等等,但缺少用于匹配源/目标版本的API
    2. 在签名中,传递XML而不是对象(糟糕,传递XML中的转义XML,双重序列化,反序列化/解析,取消分析)
    3. 其他:例如,document/literal/ws-i是否有补救措施?
    4 回复  |  直到 12 年前
        1
  •  22
  •   Justin Niessner    14 年前

    我更喜欢Salesforce.com版本控制方法。Web服务的每个版本都有一个不同的URL,其格式如下:

    http://api.salesforce.com/{version}/{serviceName}
    

    因此,Web服务的URL如下所示:

    http://api.salesforce.com/14/Lead
    
    http://api.salesforce.com/15/Lead
    

    等等……

    通过这种方法,您可以获得以下好处:

    1. 你总是知道你在说哪个版本。

    2. 保持向后兼容性。

    3. 你不必担心依赖性问题。每个版本都有完整的服务集。您只需确保在调用之间不混合版本(但这取决于服务的使用者,而不是作为开发人员的您)。

        2
  •  4
  •   Cheeso    14 年前

    解决方案是避免对类型进行不兼容的更改。

    例如,SystemBobject。您描述了这种类型的“版本1”和“版本2”,但它们根本不是同一类型。对此类型的兼容更改只涉及 添加 属性,而不更改任何现有属性的类型。假设的“版本更新”违反了这两个约束。

    通过遵循这一条Guildeline,您可以避免您描述的所有问题。

    因此,如果这是版本1中的类型定义

    class SystemBObject{  // version 1
        String key;  
        Integer year;  
        Integer month;  
        Integer day;  
    
        ... // getters and setters etc;  
    }  
    

    那么,这不能是v2中的类型定义:

    // version 2 - NO NO NO 
    class SystemBObject{ 
        String key; 
        BDate date; 
        ... // getters and setters etc; 
    } 
    

    …因为它消除了现有的字段。如果这是您需要做的更改,那么它不是新的“版本”,而是一个新的类型,应该以代码和序列化格式这样命名。

    另一个例子:如果这是您现有的v1类型:

    class SomethingFromARequest {   
        Integer requestedId;   
        ... // getters and setters etc;      
    }   
    

    …则这不是该类型的有效“v2”:

    class SomethingFromARequest {   
        Long requestedId;   
        ... // getters and setters etc;      
    }   
    

    …因为您更改了现有属性的类型。

    这些限制被更详细地解释,主要是以技术中立的方式 Microsoft's Service Versioning article .


    除了避免不兼容的来源之外,您还可以并且应该在类型中包含一个版本号。这可以是一个简单的序列号。如果您习惯记录或审核消息,并且带宽和存储空间不是问题,那么您可能需要使用UUID来增加简单整数,以标识类型的每个唯一版本的实例。

    此外,通过使用LAX处理,您可以设计数据传输对象的前向兼容性,并将“额外”数据映射到“额外”字段中。如果XML是序列化格式,那么当v1服务收到v2请求时,可以使用xsd:xml any或xsd:any和processcontents=“lax”捕获任何无法识别的架构元素。( more )如果您的序列化格式是JSON,具有更开放的内容模型,那么这是免费的。

        3
  •  3
  •   Harper Shelby damiankolasa    14 年前

    我知道这场比赛已经晚了,但我已经深入地研究了这个问题。我真的认为 最好的 答案涉及到另一个难题:服务中介。微软的托管服务引擎就是一个例子——我相信其他引擎也存在。基本上,通过更改Web服务的XML名称空间(如链接的文章所述,要包括版本号或日期),您允许中介将各种客户端调用路由到适当的服务器实现。MSE的另一个(而且,imho,非常酷)特性是能够执行基于策略的转换。您可以定义将v1请求转换为v2请求的XSLT转换,以及将v2响应转换为v1响应的XSLT转换,从而允许您在不破坏客户端实现的情况下退出v1服务实现。

        4
  •  3
  •   Neil McF    14 年前

    我认为还需要记住的是您的客户群、您是公开发布此服务,还是仅限于一组已知代理?

    我参与了后一种情况,我们发现通过简单的沟通/监视来解决这一问题并不难。

    虽然它只是与您的问题间接相关,但我们发现基于兼容性的版本控制编号似乎工作得很好。以A.B.C.为例…

    • A:需要重新编译的更改(破坏向后兼容性)
    • B:不需要重新编译的更改,但没有这样做就没有其他功能可用(新操作等)。
    • C:对底层机制的更改不会改变WSDL