Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backwards compatible service interfaces without updating older clients in azure service fabric

I have a service fabric stateful service which exposes an interface like :

public interface IAction 
{
   Task GetCustomer (Customer customer)
} 

The Customer class looks like

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

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

I have now shared the assembly containing the above model and interface with the client of the service via nuget.

After a while I have a need to update the Customer class for other clients, so I do the following, by adding the extra nullable property

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

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

   [DataMember]
   public ulong? Salary {get;set;}
}

Since I have added a nullable data member, I would assume that I need only share this new model and contract with the newer clients and the first client need not bother updating.

However, I notice that I get the following exception :

{"Interface id 'xxxxxxxx' is not implemented by object '**'"}

After having read multiple SO answers(here,here), I am led to the conclusion that always a client must have the exact reference of the interface and model present in the current running version of the service.

This is quite a big limitation, as I should not be forced to update all clients. Extra optional parameters added should not force the old clients to be updated, especially if the service can guarantee full backwards compatibility.

Is there a way around this problem of updating the service interface in a backwards compatible way without having to update the older clients ?

like image 513
Vicky Avatar asked Jan 18 '18 05:01

Vicky


3 Answers

This is quite a big limitation, as I should not be forced to update all clients. Extra optional parameters added should not force the old clients to be updated, especially if the service can guarantee full backwards compatibility.

It is not a limitation at all because one, the service is not supposed to know about the client and two, the addition of operations/members are not considered breaking because the client need not know about the addition.

Contract changes in a service is considered "non breaking". The links to other similar problem on SO that you have shared are not exactly addressing your problem, which can be categorized into the following bullet points:

  1. Service Discovery
  2. Service Versioning
  3. Implement IExtensibleDataObject (if the problem is caused by the data/data type after a roundtrip with the new addition to the service)

1 & 2 are about the client discovering the service correctly, the client consuming your service needs to be aware of it for lax versioning compatibility ie., you need to confirm that the client is not performing any schema validation against the old service before even making a call to your service. If this be the case, then you need to use explicit XML namespaces and define new contracts and new service definitions.

This is not a limitation but keeping in with strict versioning plus it is also about the data types unknown to the earlier client binding with your service that faults with an exception due to a callback, which is reasonable and you should accept it has nothing to do with the SOA.

To use this solution, you may need to define your contract and service in the following way:

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 {...}  
}  

and another version for the newly added member like so,

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 { ... }  
}  

For the source of this code, you may refer to this link which will definitely help you understand what is wrong with your service and how to modify it.

Just added this as an afterthought to isRequired attribute to a DataMember, which is false by default.

The below is referenced from here.

If a default value of null or zero for the member is unacceptable, a callback method should be provided using the OnDeserializingAttribute to provide a reasonable default in case the member is not present in the incoming stream.

[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");
    }
}
like image 188
user2347763 Avatar answered Oct 21 '22 08:10

user2347763


To support multiple client with the same remoting service you would need two different well defined contracts/interfaces. This is as per design of Remoting in service fabric.

Generally you should allow remoting for only service communication within the fabric cluster. The reason is because

A - DataContractSerializer is tightly coupled with service fabric. And I am assuming your client don't know or need to know what framework you are using for your service hosting.

B - If you are exposing the remoting interface it will restrict the functionality to client which have access to tcp port (within the domian/network)

My suggestion for implementing versioning will be to expose well defined Web Api 'JSON/REST' endpoints for your service. Where you can easily differentiate between V1 and V2 and at the same time you will be able to use the single 'Customer' class.

  • Version V1 will exposse the method with only Id and Name
  • Version V2 will expose the method along with any newly added data member's.

If for some reason you still want's to utilize remoting for your service along with versioning. You should look to deploy your service in a exclusive process which allows deployment of multiple version of same service. This deployment model can be useful in two scnearios

  • You want to maintain multiple versions of applications
  • You want to support isolated applications for multiple client in a single cluster (to save cost).

Read more about application deployment at following link

https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-hosting-model

https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-concepts-scalability

like image 24
Satya Tanwar Avatar answered Oct 21 '22 06:10

Satya Tanwar


This is a limitation of remoting, the caller and the sender must clearly understand the same message(version) to be able to communicate, if one does not understand each other, this exception will be thrown.

If you must have this flexibility, you could:

  • Try use Remoting V2 and Implement a custom serialization. See Here
  • Or, Implement your own ServiceInstanceListener using a different approach to communicate and serialize the message. You could use http communication, sockets. See Here
  • Or, You could create different contracts for new versions of you message and resolve the new ones when you use it. (is a suggestion if the other two does not fit your requirements).
like image 38
Diego Mendes Avatar answered Oct 21 '22 08:10

Diego Mendes