Download the code for this post here.
Sometimes you want to build a distributed application, not because you’re adopting a Service-Oriented Architecture, but simply because for some reason part of your code need to run on one machine and the rest of it needs to run on another. A classic example (now consigned to history by HTML5) is the old thick-client desktop application which would make service calls to a middle tier. In modern times, you might find yourself writing a native mobile application which needs to connect to an online service. Or, in a cloud scenario, you might want to avoid running your precious business-logic code on a machine that is directly accessible to the outside world, so you put your presentation layer on a machine with a public IP address and make it call a service on another machine which isn’t available publicly.
In this post, I want to promote the idea that just because an application is distributed doesn’t mean you have to actually write client and server code. With WCF, you can just define a single interface and get the framework to do all the plumbing for you. In the language of Domain-driven Design, we would say that
Process boundary != Context boundary.
I find this point is worth making explicit because on several occasions I’ve seen implementations that are structured like this:
(At runtime there is some WCF magic which creates a proxy object conforming to the IClientContract interface which calls the ServiceImplementation accross process boundaries.)
As an emotionally fragile architect, it’s not going too far to say that I find this architecture upsetting. What I dislike about it is all the duplicated code. I don’t mean the implementations being separated from the classes: I thoroughly approve of this practice for the purpose of testability and the wider principle of dependency inversion. I mean that there are far too many interfaces with no material differences.
Take IServiceContract and IClientContract in the first instance. If you’ve used ‘Add Web Dependency’ then this interface will have been generated for you; if you’re using ChannelFactory then you’ll have written it yourself. In either case, the interfaces will be interchangeable. So the following refactoring would cut down on the duplication of code:
If you want to do this you have to use ChannelFactory rather than ‘Add Web Reference’, because adding web references auto-generates the client-side interfaces.
This is better, because you’ve removed one interface. But I would recommend going further and actually making your Business Logic layer into your Service Endpoint layer.
Now you have no more classes than you would have written had everything been in the one process. A downside is that your Business Logic layer now depends indirectly on the WCF libraries, and the interfaces have [ServiceContract] attributes. If you can’t live with this then you’ll have to go back to the intermediate refactoring.
So I’ve told you how to deconstruct an over-engineered implementation. Here is my recipe for writing this pattern from scratch:
1 business problem
1 requirement for inter-machine communication
A small handful of .NET hosting environments (pick from a variety of development, system test, UAT, pre-prod and production)
- Write your Business Logic Layer and presentation layer as if there were no WCF involved.
- Separate out the interfaces of your Business Logic Layer into their own assembly.
- Put [ServiceContract] and [OperationContract] attributes on your interfaces.
- Create .svc classes inheriting from your Business Logic Layer classes
- Host your .svc files in a Web application, Windows Service or self-host in a console application, although I have to admit that my knowledge of the latter two options is purely theoretical.
- In your start-up code for your presentation layer, create a Factory class which will create clients of the interfaces you defined in step 2.
I’ve created an sample application which shows this architecture. It does something very, very slightly elevated from the trivial – it hosts and calls a service that uses Euclid’s algorithm to calculate the highest common factor of two integers. Maybe for the next iteration I’ll add something really advanced such as a fast primality test.
I want to make it clear that you aren’t preventing your service from being a worthy participant in an SOA just because you’ve used the same code in your client side. After all, exactly the same URLs are being used and the same messages are going accross the wire. And how do you make sure that this service is suitable for an SOA? Document the interface and put it under change control. That’s it. The difference between a loosely-coupled interface and a tightly-coupled one is governance, not implementation.
In later posts I will do something like the following:
- Using Castle Windsor to create the client proxies for you and inject dependencies into the service classes.
- How to cope with an IoC which doesn’t have a built-in WCF facility.
- Good practices drawn from Domain-driven Design which also help you easily make your domain layer into a WCF service.
- Discussion on whether you need to worry about closing your connections and what to do if you can’t stop worrying.