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

21
我们有两个不同的产品需要通过Web服务相互通信。 支持API版本控制的最佳实践是什么?
我找到了一篇来自2004年的文章this article,声称没有实际标准,只有最佳实践。是否有更好的解决方案?你如何解决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;
}

失败场景

如果版本1System A 客户端调用版本2System B 服务,可能会出现以下问题:

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

如果版本2System A 客户端调用版本1System B 服务,可能会出现以下问题:

  • SomethingFromBRequest中的BDate类型未知(客户端使用新的B请求对象,但是B版本1不识别)
  • 如果System A客户端足够聪明地使用请求对象的版本1,则可能会在SystemBObject对象的缺少方法(getDate())上失败。

如果版本1System B 客户端调用版本2System A 服务,可能会出现以下问题:

  • SystemAObject的类型不匹配或溢出(返回Long但期望Integer)
如果版本2System B客户端调用版本1System A服务,可能会出现以下故障:
  • SystemARequest上的类型不匹配或溢出(请求Long而不是Integer
  • 如果请求以某种方式传递,则存在强制转换问题(存根为Long,但服务返回一个Integer,在所有WS实现中不一定兼容)

可能的解决方案

  1. 在升级版本时使用数字:例如SystemAObject1SystemBRequest2等,但这缺少匹配源/目标版本的API
  2. 在签名中传递XML而不是对象(令人讨厌,在XML中传递转义的XML,双重序列化,反序列化/解析,取消解析)
  3. 其他:例如文档/字面/ WS-I有补救措施吗?

+1 - 详细清晰的问题 - ruchirhhi
我决定接受Justin的答案,尽管它是主观的,但两个答案都是很好的优秀示例。 - Eran Medan
4个回答

25

我更喜欢Salesforce.com的版本控制方法。每个Web服务版本都以以下格式获得不同的URL:

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

因此,您将拥有类似于以下 URL 的 Web 服务:

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

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

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

  1. 始终知道正在使用哪个版本。

  2. 保持向后兼容性。

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


1
根据我们的经验,这是解决问题的最佳方式。 - NotMe
此外,您可以使用反向代理并部署多个 Web 服务器,将不同版本的主机分配给不同的 URL,以便在维护时更加轻松。如果您可以为应用程序创建映像文件并自动化部署,则会更好。 - Muhatashim

4
解决方案是避免对类型进行不兼容的更改。
以SystemBObject为例,您描述了该类型的“版本1”和“版本2”,但它们根本不是相同的类型。对此类型进行兼容性更改仅涉及添加属性,而不更改任何现有属性的类型。您假设的“版本更新”违反了这些约束。
通过遵循这个准则,您可以避免所描述的所有问题。
因此,如果这是您在版本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的服务版本控制文章中以技术中立的方式有更详细的解释。


除了避免不兼容性的问题外,您还应该在类型中包含版本号,可以使用简单的序列号来表示。如果您习惯于记录或审计消息,并且带宽和存储空间不是问题,那么您可能需要使用UUID来标识每个独特版本类型的实例,与简单的整数相结合。

此外,您可以通过使用宽松处理并将“额外”的数据映射到“额外”字段中,来设计支持向前兼容的数据传输对象。如果XML是序列化格式,则您可以使用xsd:xmlAny或xsd:any和processContents="lax"来捕获任何未识别的模式元素,当v1服务接收到v2请求时(详见)。如果您的序列化格式是JSON,则由于其更开放的内容模型,这是免费的。


4
我想补充一下,请求类中的新属性应始终为可选项(minOccurs=0),否则会破坏现有客户端的代码。 - Dmitry Ornatsky
@Cheeso - 由于我不是W/S专家,而且可能会错过一些东西,根据我的小经验,如果您只是向System A的对象添加属性,则System B中的版本1客户端仍然可以正常工作。但是,如果System B中编译为System A服务的版本2的客户端调用System A服务的版本1 - 它可能会在SOAP级别上失败(意外的属性/元素等)- 我们使用GSoap(C ++ GSoap客户端与Java Axis服务)。我认为这就是Dimitry在他的评论中所说的,对吗? - Eran Medan
在消息类中,新添加的属性的minOccurs应该为零。 - Cheeso

4
我认为另一个需要考虑的因素是您的客户群,您是将此服务公开发布,还是限制在一组已知代理中?
我参与了后者的情况,并且我们发现通过简单的沟通/利益相关者管理可以解决这个问题。
虽然这只是间接与您的问题有关,但我们发现基于兼容性来确定版本号似乎非常有效。以A.B.C为例...
A: 需要重新编译的更改(破坏向后兼容性) B: 不需要重新编译的更改,但没有这样做就无法使用的其他功能(新操作等) C: 对不改变WSDL的基础机制进行更改

3

我知道这已经有点晚了,但我一直在深入研究这个问题。我真的认为最好的答案涉及另一个谜题:服务中介。Microsoft的Managed Services Engine就是其中之一 - 我确定其他类似的服务也存在。基本上,通过更改您的Web服务的XML命名空间(例如包含版本号或日期,如链接的文章所述),您允许中介能够将各种客户端调用路由到适当的服务器实现。 MSE的另一个(并且在我的看法中非常酷)功能是执行基于策略的转换。您可以定义XSLT转换,将v1请求转换为v2请求,并将v2响应转换为v1响应,从而使您能够退役v1服务实现而不会破坏客户端实现。


谢谢,但你提到的链接文章在哪里? :) - Eran Medan
我指的是你在原问题中提供的链接文章 - 这也是我开始探索的起点。 - Harper Shelby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接