Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

WCF Backward Compatibility and Versioning Strategies – Part 3

4.88/5 (7 votes)
22 Mar 2012CPOL13 min read 72.6K   696  
The versioning strategies supported by the WCF framework in different cases.

Introduction

In the first part of the article WCF Backwards Compatibility and Versioning Strategies – Part 1, I explored the effects of changes in Service Contract in different scenarios. In the second part of the article WCF Backwards Compatibility and Versioning Strategies – Part 2, I explored WCF framework’s behavior in different cases of versioning of Data Contracts. In this article, I will explore the versioning strategies supported by the WCF framework in different cases.

Backward Compatibility and Versioning Strategies

If you have gone through the previous two articles, it is understood that WCF is version tolerant. Version tolerance is acceptable in case of new operations, or adding and removing operation parameters. But at the same time, it should be avoidable when a breaking change is introduced in the service, such as removing operations from a service contract, or adding or removing required members from a data contract. Keeping all those scenarios in mind, a service architect should adopt a proper versioning strategy, which will keep pace with agility, productivity, and change control.

Followings are the versioning strategies adopted in different scenarios:

  1. Agile Versioning
  2. Semi-Strict Versioning
  3. Strict Versioning

Let’s explore how we can implement each of this versioning strategies:

Agile Versioning

This kind of versioning is based on the version tolerant behavior of the WCF framework. This versioning approach fully relies on backward compatibility for as long as possible and avoids formal contract and endpoint versioning until compatibility is broken. In the agile approach to versioning, we make changes to existing data contracts and service contracts without versioning them, or supplying new endpoints. The following figure shows the agile versioning approach:

Image 1

Figure 1. Agile versioning through the same service contract and endpoint

Let’s try to understand the approach through an example. This approach is useful in agile environments that require frequent updates to production code.

Implementing a WCF service

Let’s develop a simple WCF service project. The WCF service has a single method UpdateEmployeeData() which has a parameter and return type of the user defined type EmpInfo.

The Data Contract EmpInfo is as follows:

C#
[DataContract]
public class EmpInfo
{
   [DataMember]
   public int EmpID;
   [DataMember]
   public string EmpName;
}

The service implementation is as follows:

C#
namespace MyWcfService
{
    public class MyService : IMyService
    {
        public EmpInfo UpdateEmployeeData(EmpInfo info)
        {
            return new EmpInfo { EmpID = info.EmpID, EmpName = info.EmpName };
        }
    }
}

Build the service.

Versioning practices

Once you are done with the development of the WCF service, build the service. Then right click -> View in Browser on the file MyService.svc in order to check the WSDL generated by the WCF service. Here you can observe the WCF framework has provided the default versioning specification for the service.

Contracts:

Image 2

By default, the name of a service contract is the name of the interface. Its default namespace is "http://tempuri.org", and each operation’s action is "http://tempuri.org/contractname/methodname". It is recommended that you explicitly specify a name and namespace for the service contract, and an action for each operation to avoid using "http://tempuri.org" and to prevent interface and method names from being exposed in the service’s contract. Let’s implement our own versioning specifications across Data Contracts and Service Contracts. For this we need to add attributes as follows:

Service contract with versioning specification

C#
[ServiceContract(Name = "ServiceAContract", 
   Namespace = "http://www.mynamespace.com/samples/2012/03")]
public interface IMyService
{
  [OperationContract]
  EmpInfo UpdateEmployeeData(EmpInfo info);
}

Data contract with versioning specification

C#
[DataContract(Name = "EmpInfo",Namespace="http://schemas.mynamespace.com/samples/2012/03")] 
public class EmpInfo
{
    [DataMember]
    public int EmpID;
    [DataMember]
    public string EmpName;
   
}

Now, build the service and again view the WSDL and you can see your own versioning specification:

Image 3

Develop a console application in order to test the service.

Right click on References->Add Service Reference. Click on Discover or type the service URL in the Address box. Once you click on OK, the stub code will be generated. You can see the versioning information is used in the “Reference.cs” file in the stub code.

Excerpts from the stub code:

Image 4

Excerpts from the stub code:

Image 5

Note: When a new versioning specification is implemented at the service end and the service reference is not updated, the consumer client becomes incompatible due to the mismatch in the above mentioned section of the stub code.

Now call the service in the console application using the following code:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WCFTestClient.MyService;
using System.Runtime.Serialization;

namespace WCFTestClient
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceAContractClient proxy = new ServiceAContractClient())  
            {
                
                var sc = proxy.UpdateEmployeeData(new EmpInfo { EmpID = 1, EmpName = "Kausik" });
                Console.WriteLine("Employee ID: " + sc.EmpID);
                Console.WriteLine("Employee Name: " + sc.EmpName);
                      
            }
            Console.ReadLine();  

        }
    }
}

Run the console application. The following output should be generated:

Image 6

Change request for the service

Say, the above developed service has been deployed in production and the client applications which have consumed the service are working fine. Now, after a few days, one of the clients raises a request to update and display the employee address in its application. Also, it wants a new functionality for retrieving data by EmpID. The Data Contracts at the service end are required to be modified in order to include the employee address. Also, we need to introduce a new method for fetching the employee details by ID at the service end. At the same time, we are required to maintain compatibility for other clients. In this scenario, we can add a non-required DataMember in the EmpInfo Data Contract and a new method GetEmployeeDatabyID() as follows:

Modified Data Contract:

Image 7 

Modified Service Contract:

C#
[ServiceContract(Name = "ServiceAContract", 
        Namespace = "http://www.mynamespace.com/samples/2012/03")] 
public interface IMyService
{
    [OperationContract]
    EmpInfo UpdateEmployeeData(EmpInfo info);

    //Adding new method 
    [OperationContract]
    EmpInfo GetEmployeeDatabyID(int EmpID);
    
}

Note: Since we are following the agile versioning approach, we are deliberately avoiding introducing a new version specification for the Service Contract and Data Contract.

Modified Service implementation:

C#
namespace MyWcfService
{
    public class MyService : IMyService
    {
        public EmpInfo UpdateEmployeeData(EmpInfo info)
        {
            return info;
        }

        public EmpInfo GetEmployeeDatabyID(int EmpID)
        {
            if (EmpID == 1)
                return new EmpInfo { EmpID = 1, EmpName = "Kausik", EmpAddress = "Salt Lake" };
            else
                return null;
        }
    }
}

Build the service.

Now run the existing consumer client console application without updating the service reference. We want to call the service using the old stub code. You should get the following output:

Image 8

So we can see the existing client of the service remains unaffected on adding the new non-required members to the service Data Contracts.

Now let’s simulate another client which is interested in displaying employee address. This client should update the service reference in order to get the new functionality of updating the employee address through the method UpdateEmployeeData and the new method GetEmployeeDatabyID.

Let’s develop one more console application in order to simulate the scenario with another consumer of the service. Although we are creating the application at this stage, it is presumed that the application is an existing client which wants an additional functionality for updating the employee address and retrieving employee details by ID.

Once the application is created, right click on References->Add Service Reference. Click on Discover or type the service URL in the Address box. Once you click on OK, the stub code will be generated. Now call the service in the console application using the following code:

Image 9

Run the console application. The following output should be generated:

Image 10

So we can see that both the clients are working seamlessly after provision of a new version with changes in Service Contract and Data Contracts at the service end. One client is using the old functionalities while another uses the new ones. Although the existing clients continue to work gracefully, there are the following problems that surface with version tolerance:

  1. Adding new operations in the Service Contract
  2. Say a service has been deployed in production which is being consumed by several clients. Now if a new service method is added, the WSDL at the service end will get updated. Now all the service consumers need not update the service reference in order to refer to the latest WSDL. Only those consumers who require the new functionalities should update the service reference in order to consume them at the client. So it is impossible to track which of the existing clients have updated their WSDL unless a new endpoint has been exposed when changes are made.

  3. Adding non-required members to the Data Contract
  4. As we have seen in the earlier examples, if non-required members are added to a Data Contract and the stub code is not updated at the client, the default values are passed or used for the client. In order to initialize the missing values to some relevant values, it might require writing extra code.

  5. Removing non-required members from Data Contracts
  6. In the previous article, we also discussed, if a DataMember is removed from the Data Contract at the service end and it is present in the stub code, due to non-updating of the service, the member sent from the client goes through a round trip. Due to this issue, data passed to services or returned to clients is lost.

The agile versioning approach should be adopted only after considering all these scenarios.

Semi-Strict Versioning

This approach lies somewhere between agile and strict versioning. In this approach of versioning, formal contract and endpoint versioning is specified. Changes are tracked when contracts or endpoints are modified.

In one variation of the semi-strict approach, new operations are added to a new Version 2 service contract that inherits the Version 1 service contract. Versioning is achieved by exposing a new endpoint for the Version 2 service contract. The namespace of the existing operations remain that of the original contract, which means that Version1 clients can move to the new endpoint without impact to the existing code, and update their proxies to reflect new operations at their choice. Figure 2 illustrates a variation of this semi-strict approach to versioning:

Image 11

Figure 2: Versioning service contracts with inheritance at a new endpoint

Let’s try to understand the approach through an example.

Implementing a WCF service

Let’s develop another simple WCF service project. The WCF service has a Service Contract and Data Contracts as in the previous examples. We have also provided a versioning specification in the service.

Service contract with versioning specification:

C#
[ServiceContract(Name = "ServiceAContract", 
    Namespace = "http://www.mynamespace.com/samples/2012/03")] 
public interface IMyService
{
    [OperationContract]
    EmpInfo UpdateEmployeeData(EmpInfo info);
}

Data contract with versioning specification

C#
[DataContract(Name = "EmpInfo", 
    Namespace="http://schemas.mynamespace.com/samples/2012/03")] 
public class EmpInfo
{
    [DataMember]
    public int EmpID;
    [DataMember]
    public string EmpName;
}

The service implementation is as follows:

C#
namespace MyWcfService
{
    public class MyService : IMyService
    {
        public EmpInfo UpdateEmployeeData(EmpInfo info)
        {
            return info;
        }
    }
}

Also, modify the web.config file in order to expose an endpoint:

Image 12

Build the service.

Now call the service in another console application (WCFClient_Semi_Strict_Ver1 in the code sample). The service should be called and run gracefully.

Now, say, as in the earlier scenario, after a few days we are required to ship another version of the service with a new method GetEmployeeDatabyID. As per semi – strict versioning policy, we need to specify a formal contract and endpoint version in order to track the changes. Let’s introduce a new interface inheriting from the existing one:

New service contract with new versioning specification

Image 13

Note: The Name property of the ServiceContractAttribute is the same for both versions of the contract, while the interface type name changes from IMyService to IMyService_Ver2.

Modify the service implementation. We can implement the service from  the new interface IMyService_Ver2 as it has been derived from the existing interface IMyService.

Image 14

Change web.config in order to expose the new endpoint IMyService_Ver2:

Image 15

Now, with this change in the service end, the existing client can remain compatible as the service contract IMyService contains the old versioning specification. The clients who are interested in the new functionality GetEmployeeDatabyID can upgrade their service reference in order to consume the new endpoint. The namespace of the operation UpdateEmployeeData remains that of the original contract, which means that Version1 clients can move to the new endpoint without impact to existing code, and update their proxies to reflect new operations of their choice.

Let’s develop one more console application (WCFClient_Semi_Strict_Ver2 in the code sample) in order to simulate the scenario with another consumer of the service. Although we are creating the application at this stage, it is presumed that the application is an existing client which wants an additional functionality. Refer to the modified service in the console application. Now call the existing as well as the new service methods in the application using the following code:

C#
using (ServiceAContractClient proxy = new ServiceAContractClient())
{
    var sc = proxy.UpdateEmployeeData(new EmpInfo { EmpID = 1, EmpName = "Kausik" });
    Console.WriteLine("Employee ID: " + sc.EmpID);
    Console.WriteLine("Employee Name: " + sc.EmpName);
       
    //Implementing new functionalities in the client application
    sc = proxy.GetEmployeeDatabyID(1);
    Console.WriteLine("Retrieving Employee Details:");
    Console.WriteLine("-------------------------");
    Console.WriteLine("Employee ID: " + sc.EmpID);
                
}
Console.ReadLine();

Run the console application.  It should display the following output:

Image 16

Now run another existing client (WCFClient_Semi_Strict_Ver1 in the code sample) without updating the service reference. The existing application should run seamlessly as it remains compatible to the service.

Another variation of Semi- strict versioning – Exposing a Single Endpoint

In another variation of the semi-strict approach, new operations are added to a new Version 2 service contract that inherits the Version 1 service contract. Because the Version 2 service contract inherits the Version 1 contract, the original namespace is preserved for existing operations. The new namespace is used only for new operations. Thus, even if the original endpoint is updated to expose the Version 2 contract, messages sent from Version 1 clients will still be compatible with the original namespace. Only Version 2 clients will send messages to new operations using the new namespace. In the above implementation, we have exposed a new endpoint while adding the new service contract. Following this variation, we could have versioned service contracts with inheritance at the same point. Figure 3 illustrates another variation of the semi-contract version:

Image 17

Figure 3: Versioning service contracts with inheritance at the same endpoint.

To test this approach, modify web.config in order to expose a single endpoint with the new contract IMyService_Ver2 which has inherited the existing contract IMyService.

Image 18

Now run both the consumers of the service. You can see them running gracefully without being affected with the change in the service end.

Strict Versioning

In a strict version, formal versioning specification is required for any and all changes to service contracts. In some scenarios, formal versioning is a must. For example, if you remove service operations. By delaying formal versioning, you save on development effort for insignificant changes but you lose the ability to trace versions of services you have exposed and track the migration of clients to updated service functionality. In this type of versioning, a new endpoint is exposed whenever a service method is removed or added. Even if the existing operations are not changed, with strict versioning, the Version 2 contract must include all existing operations under the new namespace. Figure 4 illustrates another variation of the semi-contract version:

Image 19

Figure 4: Strict versioning through a new service contract and unique endpoint

Let’s try to understand the approach through an example:

Implementing a WCF service

Let’s develop another simple WCF service project. The WCF service has a Service Contract and Data Contracts as in the previous examples. We have also provided versioning specification in the service.

Service contract with versioning specification

C#
[ServiceContract(Name = "ServiceAContract", Namespace = "http://www.mynamespace.com/samples/2012/03")] 
public interface IMyService
{
    [OperationContract]
    EmpInfo UpdateEmployeeData(EmpInfo info);
}

Data contract with versioning specification

C#
[DataContract(Name = "EmpInfo",Namespace="http://schemas.mynamespace.com/samples/2012/03")] 
public class EmpInfo
{
    [DataMember]
    public int EmpID;
    [DataMember]
    public string EmpName;
}

The service implementation is as follows:

C#
namespace MyWcfService
{
    public class MyService : IMyService
    {
        public EmpInfo UpdateEmployeeData(EmpInfo info)
        {
            return info;
        }
    }
}

Also, modify the web.config file in order to expose an endpoint:

Image 20

Build the service.

Now call the service in another console application (WCFClient__Strict_Ver1 in the code sample). The service should be called and run gracefully.

Now, say, as in the earlier scenario, after a few days, we are required to ship another version of the service with a new method GetEmployeeDatabyID. As per strict versioning policy, we need to specify a formal contract and endpoint version in order to track the changes. Let’s introduce a new interface with existing service methods as well as a new method:

New Service Contract with new versioning specification

Image 21

Note: The Name property of ServiceContractAttribute is the same for both versions of the contract, while the interface type name changes from IMyService to IMyService_Ver2.

Modify the service implementation. We can implement the service from the new interface IMyService_Ver2.

C#
public class MyService_Strict : IMyService, IMyService_Ver2
{
        public EmpInfo UpdateEmployeeData(EmpInfo info)
        {
            return info;
        }

        public EmpInfo GetEmployeeDatabyID(int EmpID)
        {
            if (EmpID == 1)
                return new EmpInfo { EmpID = 1, EmpName = "Kausik" };
            else
                return null;
        }
}

Change web.config in order to expose the new endpoint IMyService_Ver2.

Image 22

Let’s develop one more console application (WCFClient_Strict_Ver2 in the code sample) in order to simulate the scenario with another consumer of the service. Although we are creating the application at this stage, it is presumed that the application is an existing client which wants an additional functionality. Refer to the modified service in the console application. Now call the existing as well as the new service methods in the application using the following code:

C#
static void Main(string[] args)
{
    using (ServiceAContractClient proxy = new ServiceAContractClient())
    {
        var sc = proxy.UpdateEmployeeData(new EmpInfo { EmpID = 1, EmpName = "Kausik" });
        Console.WriteLine("Employee ID: " + sc.EmpID);
        Console.WriteLine("Employee Name: " + sc.EmpName);
    }

    using (ServiceAContract1Client proxy = new ServiceAContract1Client())
    {
        //Implementing new functionalities in the client application
        var mc = proxy.GetEmployeeDatabyID(1);
        Console.WriteLine("Retrieving Employee Details:");
        Console.WriteLine("-------------------------");
        Console.WriteLine("Employee ID: " + mc.EmpID);
    }

    Console.ReadLine();  
}

Run the console application. The service methods should be called and the application should run gracefully. Also, check another existing client (WCFClient__Strict_Ver1 in the code sample). The client should be unaffected and run gracefully.

Conclusion

I have explored different approaches for versioning. Try to use versioning strategies as per your requirements. The following table suggests a few versioning strategies based on the scenario:

Versioning StrategiesScenario
Agile versioningAppropriate for an environment that relies heavily on backward compatibility and must tolerate frequent changes.
Strict versioningAppropriate for an environment that is less frequently updated or requires strict change control.
Semi-strict versioningAppropriate for an environment which allows customized versioning policy that meets application’s needs.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)