WCF service contracts are like legal contracts. Once released, they are set in stone and should never be broken. If there are client applications which consume your service and you decide to change one of the operation signatures on the contract, it will break the clients because they were built to use the original contract interface. So the only option you have is to add additional methods to the contract while leaving the original interface members intact. This is the Open/Closed principle (the ‘O’ in SOLID) – open to extension but closed to modification.
However over time, such extensions can lead to badly designed interfaces. Because WCF services don’t support operation overloading, you might have multiple similarly named service operations which take different parameters for what is essentially the same method. This isn’t a good situation to be in, and over time your once clean interface will turn into a confusing mess. I once had to write an application which used commercial third-party financial services with the most appalling interfaces, because they had so many different versions of the same method. It was a nightmare, so don’t do this to your clients!
To avoid going down the path of interface bloat, you need to get into the habit of using specific request and response types in your service contracts right from day one. Here’s an example of a service contract before and after doing this to show how much it is improved.
Here’s the first draft of the service contract, with a single operation which takes a data contract called
User and returns a boolean. No surprises so far.
And the service which implements the contract:
And here is the data contract passed in as the parameter:
So far all very basic stuff. I finish coding the service, deploy it to my production server and all of my clients immediately start using it. Job done. But before getting too pleased with myself, I suddenly realise that I can’t return specific information to the client application if registration fails (beyond a simple true or false). So clients have no idea what actually caused registration to fail. And worse still, I realise that I don’t actually want to pass in a
User type anymore, but a
UserRegistration type which contains more properties. Here we go down the path of expanding contracts already. But it doesn’t have to be this way…
So here is an improved version of the service contract which avoids these pitfalls. But before going into detail, just one point about returning error information. Although the original interface only returned a boolean, you could in theory pass back detailed error information back to the client. However this is not recommended outside of the development environment, especially if your service is going to be used by a third party. You absolutely don’t want third parties seeing an exception stack trace ever because it contains specific details about your code and infrastructure which should remain hidden for security reasons.
It’s only a minor change, but the operation now has dedicated request and response types which are obviously both data contracts:
Below is the implementation of the new service contract. In this particular case it prevents exceptions being thrown, and instead wraps success or failure status together with any error information in a response object which is returned to the client. I have included a try-catch block in this example just to demonstrate returning different response values. But in reality, you don’t want to be adding a try-catch block to every new service operation you create (what if you or another developer forgets?). So a better alternative is to use custom error handlers, or intercepts or policy injection via the Microsoft Enterprise library. This allows features such as exception handling, logging, etc, to be automatically added as a wrapper around the service messaging pipeline. And once the infrastructural code is in place, you can forget about it. However this is a whole separate subject so I won’t use it in this example.
This is the new request data contract. In this particular scenario all it’s doing is wrapping our original
User type, but the point is that additional data members can be added over time without having to ever change the service contract itself. Only the data contracts change.
This is the real power of this approach to service contract design. The contract remains the same but it’s the data contract parameters which are extended whenever the service needs to receive or return additional data. It also promotes very clean simple contract interfaces because no matter how much data you pass in, you only ever have a single request parameter.
And here is the response data contract together with its base type and status enum. The response type could have any number of data members added to it, but notice how the base response provides two essential properties which tell the client (a) if the service call was successful, and (b) if not, then one or more messages indicating what failed.
The base response provides everything we need for this simple example so
RegisterUserResponse doesn’t actually have any data members of its own, but again it may be extended in the future without changing the contract. And don’t be tempted to use the base response as the return type in your contracts. Always use derived request and response types specific to each particular operation, to guarantee future-proofing your service.
Using this approach to service contract design will greatly reduce the risk of you ever having to alter a service contract once it’s been published.