向后兼容的服务接口,无需更新Azure服务结构中的旧客户端

Vic*_*cky 8 client-server backwards-compatibility azure-service-fabric

我有一个服务结构状态服务,它暴露了一个界面,如:

public interface IAction 
{
   Task GetCustomer (Customer customer)
} 
Run Code Online (Sandbox Code Playgroud)

Customer类看起来像

[DataContract]
public class Customer
{
   [DataMember]
   public string Id {get;set;}

   [DataMember]
   public string Name {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

我现在通过nuget共享包含上述模型的程序集和与服务客户端的接口.

过了一段时间我需要为其他客户更新Customer类,所以我通过添加额外的可空属性来执行以下操作

[DataContract]
public class Customer
{
   [DataMember]
   public string Id {get;set;}

   [DataMember]
   public string Name {get;set;}

   [DataMember]
   public ulong? Salary {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

由于我添加了一个可以为空的数据成员,我认为我只需要共享这个新模型并与新客户签订合同,第一个客户端无需更新.

但是,我注意到我得到以下异常:

{"Interface id 'xxxxxxxx' is not implemented by object '**'"}
Run Code Online (Sandbox Code Playgroud)

在阅读了多个SO答案(这里,这里)后,我得出的结论是,客户端必须始终具有当前运行版本的服务中存在的接口和模型的确切参考.

这是一个很大的限制,因为我不应该被迫更新所有客户端.添加的额外可选参数不应强制更新旧客户端,尤其是在服务可以保证完全向后兼容的情况下.

有没有办法解决以向后兼容的方式更新服务接口而不必更新旧客户端的问题?

use*_*763 6

这是一个很大的限制,因为我不应该被迫更新所有客户端.添加的额外可选参数不应强制更新旧客户端,尤其是在服务可以保证完全向后兼容的情况下.

这根本不是限制,因为一个,服务不应该知道客户端和两个,操作/成员的添加不被认为是破坏因为客户端不需要知道添加.

服务中的合同变更被视为" 非破坏性 ".您共享的SO上其他类似问题的链接并不完全解决您的问题,可以将其分为以下要点:

  1. 服务发现
  2. 服务版本控制
  3. 实现IExtensibleDataObject(如果在使用新添加到服务的往返之后问题是由数据/数据类型引起的)

1和2是关于客户端正确发现服务,消费您的服务的客户端需要了解它的松散版本兼容性,即,您需要确认客户端在进行任何架构验证之前不会对旧服务进行验证致电您的服务.如果是这种情况,那么您需要使用显式XML命名空间并定义新合同和新服务定义.

不是一个限制,但保持严格的版本加上它也是关于早期客户端与您的服务绑定未知的数据类型由于回调而出现异常故障,这是合理的,您应该接受它无关与SOA.

要使用此解决方案,您可能需要以下列方式定义合同和服务:

public interface IPurchaseOrderV1  
{  
    string OrderId { get; set; }  
    string CustomerId { get; set; }  
}  

[DataContract(  
Name = "PurchaseOrder",  
Namespace = "http://examples.microsoft.com/WCF/2005/10/PurchaseOrder")]  
public class PurchaseOrderV1 : IPurchaseOrderV1  
{  
    [DataMember(...)]  
    public string OrderId {...}  
    [DataMember(...)]  
    public string CustomerId {...}  
}  
Run Code Online (Sandbox Code Playgroud)

和新添加的成员的另一个版本,如此,

public interface IPurchaseOrderV2  
{  
    DateTime OrderDate { get; set; }  
}

[DataContract(   
Name = "PurchaseOrder",  
Namespace = "http://examples.microsoft.com/WCF/2006/02/PurchaseOrder")]  
public class PurchaseOrderV2 : IPurchaseOrderV1, IPurchaseOrderV2  
{  
    [DataMember(...)]  
    public string OrderId {...}  
    [DataMember(...)]  
    public string CustomerId {...}  
    [DataMember(...)]  
    public DateTime OrderDate { ... }  
}  
Run Code Online (Sandbox Code Playgroud)

对于此代码的来源,您可以参考此链接,这肯定会帮助您了解您的服务有什么问题以及如何修改它.

刚刚将此作为事后想法添加到DataMember的isRequired属性,默认情况下为false.

以下是从这里引用的.

如果成员的默认值为null或零是不可接受的,则应提供回调方法 OnDeserializingAttribute,以便在传入流中不存在该成员的情况下提供合理的默认值.

[OnDeserialized]
public void OnDeserialized(StreamingContext context)
{
    if (this.id == null) throw new ArgumentNullException("id");
    if (this.Name == null) throw new ArgumentOutOfRangeException("name");
    if (this.Salary < 0) throw new ArgumentOutOfRangeException("salary");

    if (this.Salary > 0)
    {
        throw new InvalidOperationException("No child labor allowed");
    }
}
Run Code Online (Sandbox Code Playgroud)