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:
- Agile Versioning
- Semi-Strict Versioning
- 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:
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:
[DataContract]
public class EmpInfo
{
[DataMember]
public int EmpID;
[DataMember]
public string EmpName;
}
The service implementation is as follows:
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:
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
[ServiceContract(Name = "ServiceAContract",
Namespace = "http://www.mynamespace.com/samples/2012/03")]
public interface IMyService
{
[OperationContract]
EmpInfo UpdateEmployeeData(EmpInfo info);
}
Data contract with versioning specification
[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:
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:
Excerpts from the stub code:
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:
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:
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 DataMembe
r in the EmpInfo
Data Contract and a new method GetEmployeeDatabyID()
as follows:
Modified Data Contract:
Modified Service Contract:
[ServiceContract(Name = "ServiceAContract",
Namespace = "http://www.mynamespace.com/samples/2012/03")]
public interface IMyService
{
[OperationContract]
EmpInfo UpdateEmployeeData(EmpInfo info);
[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:
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:
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:
Run the console application. The following output should be generated:
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:
- Adding new operations in the Service Contract
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.
- Adding non-required members to the Data Contract
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.
- Removing non-required members from Data Contracts
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:
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:
[ServiceContract(Name = "ServiceAContract",
Namespace = "http://www.mynamespace.com/samples/2012/03")]
public interface IMyService
{
[OperationContract]
EmpInfo UpdateEmployeeData(EmpInfo info);
}
Data contract with versioning specification
[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:
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:
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
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
.
Change web.config in order to expose the new endpoint IMyService_Ver2
:
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:
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);
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:
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:
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
.
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:
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
[ServiceContract(Name = "ServiceAContract", Namespace = "http://www.mynamespace.com/samples/2012/03")]
public interface IMyService
{
[OperationContract]
EmpInfo UpdateEmployeeData(EmpInfo info);
}
Data contract with versioning specification
[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:
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:
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
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
.
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
.
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:
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())
{
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 Strategies | Scenario |
Agile versioning | Appropriate for an environment that relies heavily on backward compatibility and must tolerate frequent changes. |
Strict versioning | Appropriate for an environment that is less frequently updated or requires strict change control. |
Semi-strict versioning | Appropriate for an environment which allows customized versioning policy that meets application’s needs. |
References