This question is around how to architect WCF services to make it easy to evolve over time. Its difficult to get the depth of response to this without describing the problem.
Background
I am developing a large system of WCF services and clients. The server side is "easy" to update as there are only 10 servers in question running this code.
The clients are very difficult to update, despite the high degree of automation, at 300,000+ WCF clients, updates are something that will always take time, and only achieves a high update success rate over a period of two to three weeks.
Data Contracts
[DataContract]
public class MyContract
{
[DataMember]
public int Identity {get; set;}
[DataMember]
public string Name {get; set;}
// More members
}
The DataContract
is difficult to initialise and has a standard MyContractFactory
class to initialise obtain the appropriate instance for your machine.
public class static MyContractFactory
{
public static MyContract GetMyContract()
{
// Complex implementation
}
}
ServiceContracts
The DataContract
is very common across a range of web services.
namespace MyPrefix.WebServicve1
{
[ServiceContract]
public class IMyInterface1
{
[OperationContract]
public void DoSomethingWithMyContract(MyContract data);
}
[ServiceContract]
public class IMyInterface2
{
[OperationContract]
public void DoSomethingDifferentWithMyContract(MyContract data);
}
}
Client
My client is plugin based with plugins running in either separate processes or app domains depending on the level of trust we have in that plugin.
Implementation 1
My initial implementation of this (default WCF) ended up with the DataContract
in one assembly, ServiceContract
, and implementation in its own assembly.
The clients ended up with a very ugly,
MyWebService1.MyContract
MyWebService2.MyContract
With a copy and paste of the MyContractFactory
in nearly every plugin. Whilst the DataContract
was the same, the fact that the clients did not include the DataContract
assembly meant that it appeared under different namespaces as different objects.
Implementation 2
The clients now include the DataContract
assembly, ServiceContracts
are in a separate assembly to the service implementation, clients may include some of the ServiceContract
assemblies if it will aid with code reuse (no more copy and paste).
Question
With the second implementation I am now facing the difficulty of, how do I update my DataContract
and ServiceContracts
?
Do I update the same assembly and increment the version number? How do I preserve backwards compatibility whilst all the clients upgrade? Breaking the clients until they update is not acceptable.
Do I create a new assembly with a class that extends MyDataContract
, new methods that accept the new type under a new ServiceContract
? Does that mean for every minor change to my contracts I need a new assembly? How would I stop it from getting to literally hundreds in a couple of years time?
Some other solution?
Regardless of the solutions I think through, they all seem to have a major downside.
There doesn't seem to be (at least to me) of,
ServiceContract
(overloads of the OperationContract
need a new "name"). I already have things like the below, and it strikes me a nightmare to maintain over time.Operation Contract complexity
[OperationContract]
public void DoSomethingWithMyContract(MyContract data);
[OperationContract(Name = "DoSomethingWithMyDataByAdditionalData"]
public void DoSomethingWithMyContract(MyContract data, MyContract2 additionalData);
I am looking for a solution that has worked over a period of time in a large scale environment. Blog entries and the like are very welcome.
Update 1
Looking through the limitations of using "schemaless" changes, different namespaces seems like the only sure method. However, its not quite working as expected, e.g. below
[ServiceContract(
Name = "IServiceContract",
Namespace = "http://myurl/2012/05")]
public interface IServiceContract1
{
// Some operations
}
[ServiceContract(
Name = "IServiceContract",
Namespace = "http://myurl/2012/06")]
public interface IServiceContract2
{
// Some different operations using new DataContracts
}
With the following service
public class MyService : IServiceContract1, IServiceContract2
{
// Implement both operations
}
and the following config
<service behaviorConfiguration="WcfServiceTests.ServiceBehavior"
name="Test.MyService">
<endpoint
address="2012/05"
binding="wsHttpBinding"
contract="Test.IServiceContract1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint
address="2012/06"
binding="wsHttpBinding"
contract="Test.IServiceContract2">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
Results in two contracts with two different names, I expected that I can point my clients to,
http://myurl.com/MyService.svc/2012/05 for the old version, and http://myurl.com/MyService.svc/2012/06
, but it seems like if I want to preserve the ServiceContract name they have to be two separate services rather than separate endpoint addresses for the same service?
Update 2
I ended up using the method I have described under update 1. Whilst the WSDL looks wrong, the service is indeed backwards compatible under older clients when I've tested this.
Both Microsoft and most of the respected WCF gurus out there will probably say the same thing: versioning should be handled using the contract namespaces.
I don't mean the .NET namespaces of your assemblies - I mean the actual WCF service (and data contract) namespaces - so you should have:
[ServiceContract(Namespace="http://services.yourcompany.com/Service1/V01"]
or something the like - some folks like to version by year/month:
[ServiceContract(Namespace="http://services.yourcompany.com/Service1/2012/05"]
This allows you to have multiple versions of the same service, and as long as clients come calling with an older version (indicated by the service namespace), they'll get the old version (as long as you still expose that).
See:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With