I had some questions on versioning contracts, so I thought I'd write up a quick list of tips on this subject, some of which applies to any platform where contracts drive communications (read: web services in general).
Point #1 – breaking changes require contract versioning
Here are an example of some breaking changes that cause serialization requirements for a service to change:
- Remove operations
- Change operation name
- Remove operation parameters
- Add operation parameters
- Change an operation parameter name or data type
- Change an operation's return value type
- Change the serialized XML format for a parameter type (data contract) or operation (message contract) by explicitly using .NET attributes or custom serialization code
- Modify service operation encoding formats (RPC Encoding vs. Document Literal)
Point #2 – modifications to implementation semantics require contract versioning
Even if there are no contract versioning violations from #1, if the implementation semantics of an operation have changed this warrants contract versioning. If your clients call an Add operation and you decide that Add will no longer add, it will subtract...then your existing clients should continue to call the "old service operation" and you should create a new operation for the subtract operation. If the change is subtle, and if clients will not be affected by the change, no need to create a new operation.
Do you have to version the contract if you add a new operation? Not necessarily, however if you have a contract that has been published with methods A, B and C...and clients have been programming against that contract, then when you add methods D and E it would be good to provide a distinction between the original contract, and the new one. If the new methods are still related to the same contract, you can provide a new contract by the same name, with a new namespace.
Point #3 – you can avoid contract versioning
You can reduce the changes of #1 and #2 being directly violated. For example, a parameter can receive alternate serialization formats, the service can opt to forgive missing elements, or ignore extra elements. You can design the service contract to be forgiving by things like this:
- Using optional elements in data contracts
- Using IXmlSerializable types and “handling” serialization differences behind the scenes
- Intercepting parameter serialization and deserialization at a lower level to overcome differences
You can also avoid parameter list modification issues by avoiding specifying a list of parameters in your methods. For example instead of:
void Add(AddRequest message);
where AddRequest is a type that receives x and y:
[DataContract] public class AddRequest
[DataMember] public int x;
[DataMember] public int y;
Now, if you add another public member to the AddRequest type, and make sure it is not a required element, old clients can send their reduced set along:
[DataMember(IsRequired="false")] public int z;
You can preserve unknown serialization elements as well, on the client or on the service side, by implementing IUnknownSerializationData in your data contract types. You can find out more about this in my book chapter on Contracts, which will be posted when I launch my book blog this week.