Two types of Changes:
- Backwards-Compatible Changes (or
Non-Breaking Changes) -> Changes to services that would not require changes
to existing clients.
- Non-Backwards-Compatible Changes (or
Breaking Changes) -> Changes to services that would require changes to
existing clients.
Versioning Decision Tree
Image courtesy:
http://blogs.msdn.com/b/craigmcmurtry/archive/2006/07/23/676104.aspx
It depicts all the logically possible ways in which we might have to modify a
service, how to implement each change, and what the consequences will be.
The diamonds represent alternative ways in which a service might have to change.
The rectangles and the circle represent procedures for implementing changes. The
procedures represented by the rectangles yield backwards-compatible changes,
whereas the procedure represented by the circle results in a
non-backwards-compatible change.
Now let us trace the routes through the decision tree to understand the
implications of each alternative.
1. Adding a New Operation
This change can be accomplished using a procedure called service contract
inheritance, and the result will be a backwards-compatible change.
E.g.,
Currently, I am having this Service Contract
[ServiceContract]
public interface IEcho
{
[OperationContract]
string Echo(string input);
}
public class Service : IEcho
{
public string Echo(string input)
{
return input;
}
}
Now to add new operation we need to define a new service contract (IExtendedEcho),
with the new operations to be added to the service, which derives from the
original service contract (IEcho):
[ServiceContract]
public interface IExtendedEcho: IEcho
{
[OperationContract]
string[] ExtendedEcho(string[] inputs);
}
The next step is to have the service type
implement the new contract in addition to the original one:
public
class Service : IEcho, IExtendedEcho
{
public string Echo(string input)
{
return input;
}
public string[] ExtendedEcho(string[] input)
{
return input;
}
}
The final step is to modify the configuration of the service endpoint so that
where it referred to the original contract
<endpoint
address="Echo"
binding="basicHttpBinding"
contract="IEcho"
/>
It now refers to the derived contract with the additional methods:
<endpoint
address="Echo"
binding="basicHttpBinding"
contract="IExtendedEcho"
/>
Now new clients that are aware of the additional operations of the derived
contract can make use of those operations. Yet existing clients, which might
know about only the original service contract, could still have the operations
of that original contract executed at the same endpoint. Thus, service contract
inheritance has the happy consequence of a backwards-compatible change.
2. Changing an Operation
The second decision posed by the versioning decision tree is whether or not an
existing
operation of a service has to be modified. If so, the next decision to make is
whether or not the change that is required is a change to the data contracts of
one or more parameters.
Changing the Data Contract of a Parameter
1. Adding\Removing nonrequired data members
[DataContract (Namespace=http://WCFVersioning/2011/09)]
public class StockPrice
{
[DataMember(IsRequired=true)]
public double CurrentPrice;
[DataMember(IsRequired=true)]
public DateTime CurrentTime;
[DataMember(IsRequired=true)]
public string Ticker;
[DataMember]
public string Currency;
}
[DataContract
(Namespace=http://WCFVersioning/2011/09)]
public class StockPrice
{
[DataMember(IsRequired=true)]
public double CurrentPrice;
[DataMember(IsRequired=true)]
public DateTime CurrentTime;
[DataMember(IsRequired=true)]
public string Ticker;
[DataMember]
public int DailyVolume;
}
The addition and removal of optional data members is a backwards-compatible
change.
For existing clients to properly pass around data after new members are added,
the original data contract must support extensibility. That is, the original
contract must support serialization of unknown future data.
A Data Contract that Implements IExtensibleDataObject
[DataContract (Namespace=http://WCFVersioning/2011/09)]
public class StockPrice : IExtensibleDataObject
{
[DataMember(IsRequired=true)]
public double CurrentPrice;
[DataMember(IsRequired=true)]
public DateTime CurrentTime;
[DataMember(IsRequired=true)]
public string Ticker;
[DataMember]
public string Currency;
private ExtensionDataObject unknownData = null;
public ExtensionDataObject ExtensionData
{
get { return this.extensionData; }
set { this.extensionData = value; }
}
}
Other Changes to Data Contracts
-
Change the name or namespace of a data
contract
-
Rename an existing data member that was
previously required.
-
Add a new data member with a name that has
been used previously.
-
Change the data type of an existing data
member.
-
Add new members with IsRequired=true on
DataMemberAttribute.
-
Remove existing members with IsRequired=true
on DataMemberAttribute.
2. Adding required data members
[DataContract (Namespace=http://WCFVersioning/2011/09)]
public sealed class ExServiceDef : IExtensibleDataObject
{
[DataMember(Order = 1)]
public string UploadUrl { get; set; }
[DataMember(Order = 2)]
public string Version { get; set; }
[DataMember(IsRequired=true, Order = 3)]
public string NewRequiredField { get; set; }
#region IExtensibleDataObject Members
public ExtensionDataObject ExtensionData { get; set; }
#endregion
}
Client Request
<ServicesForUsers
xmlns="http://
WCFVersioning/2011/09">
<securitytoken>123</securitytoken>
<username>ZodiacTest</username>
</ServicesForUsers>
Service Response
<ServicesForUsersResponse
xmlns="
http://
WCFVersioning/2011/09">
<ServicesForUsersResult
xmlns:d4p1="http://WCFVersioning/2011/09/DataContracts"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:UploadUrl>\\nimittal\MRDF\JobFiles\</d4p1:UploadUrl>
<d4p1:Version>1.0.0.0</d4p1:Version>
<d4p1:NewRequiredField
i:nil="true"></d4p1:NewRequiredField>
</ServicesForUsersResult>
</ServicesForUsersResponse>
</s:Body>
The addition of required data members is a backwards-compatible change.
3. Removing required data members
The removal of required data members is a non-backwards-compatible change.
If we have removed the required data members and not updated client WSDL we will
get error message something like this:
Error Message:
The formatter threw an exception while trying to deserialize the message: There
was an error while trying to deserialize parameter http://WCFVersioning/2011/09:GetCustomerResult.
The InnerException message was 'Error in line 1 position 4874. 'EndElement' 'GetCustomerResult'
from namespace 'http://WCFVersioning/2011/09 is not expected. Expecting element
'NewRequiredField'.'. Please see InnerException for more details
3. Other Changes to Operations
Changes to existing operations:
These all changes can be dealt with by defining a new service contract
incorporating the modified operation. Revised versions of service contracts must
be disambiguated
from earlier versions by defining them in new version-specific namespaces. Also,
after a new version of a service contract has been defined, it should be exposed
at a new service endpoint. Again, exposing a new service endpoint is a
backwards-compatible change.
4. Changing a Binding
If the change to be made to a service requires changing the binding of one of
its endpoints, the versioning decision tree indicates that the modified binding
should be
exposed at a new service endpoint. Exposing a new service endpoint is a
backwards-compatible change.
5. Deciding to Retire an Endpoint
If we want to retire an existing endpoint that means we are introducing new
endpoint for all existing client. This is a non-backwards-compatible change.
There are two ways of easing the consequences of this change to a service:
-
Add a System.ServiceModel.FaultContract
attribute to all the operations of a contract to indicate that the operation
might return a fault indicating that the endpoint has been retired:
[DataContract]
public class RetiredEndpointFault
{
[DataMember]
public string NewEndpointMetadataLocation;
}
[ServiceContract]
public interface IEcho
{
[FaultContract(typeof(RetiredEndpointFault))]
[OperationContract]
string Echo(string input);
}
-
Inform all the clients using the existing
endpoint and also the operators of those clients.
Service Contract Changes
Change |
Type of Change |
Effect |
Adding a new operation |
Non-Breaking Change |
Existing client applications are
unaffected, but the new operation is
not visible to WCF client applications connecting to the service by
using a proxy generated from the WSDL description of the original
service contract.
Existing client applications that
dynamically query services and construct messages can use the new
operation. |
Removing an operation |
Breaking Change |
Existing client applications that
invoke the operation will no longer
function correctly, although client applications that do not use the
operation remain unaffected. |
Changing the name of an operation |
Breaking Change |
Existing client applications that
invoke the operation will no longer
work, although client applications that do not use the operation remain
unaffected.
Note that the name of an operation
defaults to the name of the method in the service contract. You can
change the name of a method but retain the original name of the
operation by using the Name
property in the
OperationContract
attribute of the method, like this:
[OperationContract
(Name="ListProducts")]
List<string>
ListAllProducts();
This is good practice, as it removes
any dependency between the service contract and the name of the physical
method that implements the operation. |
Changing the protection level of an
operation |
Breaking Change |
Existing client applications will not
be able to invoke the operation. |
Adding a parameter to an operation |
Breaking Change |
Existing client applications will no
longer be able to invoke the
operation, as the SOAP messages they send will be incompatible with the
SOAP messages
expected by the service. |
Reordering parameters in an operation |
Breaking Change |
The results are not easily
predictable (some existing client applications might continue to work). |
Removing a parameter from an
operation |
Breaking Change |
The results are not easily
predictable (some existing client applications might continue to work). |
Changing the types of parameter or
the return type of an operation |
Breaking Change |
Existing client applications might
continue to function, but there is a
significant risk that data in SOAP messages will be lost or
misinterpreted. This includes
applying or removing the
ref
and
out
modifiers to parameters, even if the
underlying type does not change.
|
Adding a Fault Contract to an
Operation |
Breaking Change |
Existing client applications can be
sent fault messages that they will not be able to interpret correctly. |
Removing a Fault Contract from an
operation |
Non-Breaking Change |
Existing client applications will
continue to function correctly, although any handlers for trapping the
faults specified by this fault contract will be rendered obsolete. |
Changing the Name or
Namespace property of the ServiceContract attribute for a
service contract |
Breaking Change |
Existing client applications that use
the previous name or namespace will no longer be able to send messages
to the service. |
Best Practices: