WCF the Manual Way… the Right Way (Cont.)
Consuming the Service
Service consumers can be any type of application. As in the case of old fashioned Web services, consumers need proxy classes with which to communicate with the services. Visual Studio makes it easy to have all of this created for you simply by using the “Add Service Reference” option. Once again, like ASMX Web services, you can use this feature to browse to a service and consume it from your client. Later I’ll go over everything I see wrong with this, but I’ll start by actually performing this task and examining what happens.
I created a Windows Forms application with which I will consume the service I created earlier. When I right-click on my project and choose “Add Service Reference” I can browse to the service address (Figure 1) or simply drop-down the “Discover” button and select a service in my solution (where mine happens to reside). Since this technique will create my proxy classes, service contracts, and data contracts for me on my client application, I need to choose a namespace in which to put them. For this example, I’ll leave the default of “ServiceReference1”.
Figure 1: "Add Service Reference" dialog box.
Once I add this reference, Visual Studio creates several items for me in my Windows application. First of all, Visual Studio has added a reference to System.ServiceModel to my project. You’ll also notice that there’s a project tree section now called “Service References” that contains one item called “ServiceReference1”. This is the namespace name I provided when I added the reference. Now, I told you that several files would be created for me, but they’re not visible unless I turn on “Show All Files” in my Solutions Explorer. A couple of levels deep in the file tree beneath the “Service References” you’ll find a file called Reference.cs. This file contains the three elements I spoke of earlier. It contains a class that represents the data contract the referenced service uses, an interface representing the service contract, and a proxy class which is used to access the service’s methods. Visual Studio as placed all of these in a namespace that is comprised of the project’s name and the “ServiceReference1” name that was the default in the “Add Reference” dialog. Incidentally, if I add a reference to another service, I cannot reuse the same namespace given this technique. The name of the proxy class created is the name of the service that I referenced followed by the word “Client”: Service1Client.
In order for my client application to access the service, it simply needs to instantiate the Service1Client class and access its methods, which are identically named as those defined in the IService1 interface.
The configuration automatically created for me is quite extensive. In fact, Visual Studio creates a binding configuration for me which displays every available attribute with their default value. You can observe this in Listing 1. This is nice if you want to learn about all the attributes, otherwise… well, I’ll get back to this thought later.
Using the conventional techniques for creating services and consumers, you can hit the ground running rather quickly; but is this a good thing? Let me analyze what I find wrong with everything that’s just taken place.
- Problem:Data contracts and service contracts should be in their own assembly.Reason:When consumers are .NET, the contracts can be shared by them.
- Problem:Services themselves should be in their own assembly.Reason:Decouples them from the host.
- Problem:Service hosts should be its own project.Reason:See previous reason.
- Problem:Service host’s web.config contains quite a bit.Reason:Metadata endpoint won’t be necessary for the techniques I’m planning to use.
- Problem:Client proxies should be in their own assembly.Reason:More than one client application may want to use these services.
- Problem:Adding a service reference-bad ideaReason:Service and data contracts are duplicated; little control over namespace name; over-bloated configuration.
I’ll revisit each of these and provide alternative approaches in the next section.
The Manual Way… the Right Way
Having Visual Studio do everything for you may seem nice in conversation but as you noticed above, adds a significant amount of extra details to both my service and my consumer that is simply not needed, and in my opinion not recommended. Let’s see how to do it all manually.
Now I’ll talk about one of my biggest gripes, first and foremost. Accept the fact that when you design a service-oriented system, you’re going to be dealing with many assemblies. There’s nothing wrong with this in any type of design; so long as there are beneficial reasons for it.
When I design a WCF-based system, I’ll have separate assemblies for the following components:
- Service and data contracts
- Business/ORM engines
- Service hosts
- Client proxies
- Client applications
After I describe how I like to separate out my WCF-based components, I will give you a step-by-step example by setting up an actual WCF-based application for both the service side and the client side.
The first piece of a service-oriented design I want to pull into its own assembly is the contracts. Though one of the tenets of SOA is that services should be able to service any type of client, as .NET developers I’m willing to say that most of the times (or at least many of the times) you will be dealing with .NET on both sides. In anticipation of this, I like to have both my service contracts and data contracts in their own assemblies. In the previous section I told you that Visual Studio created a copy of these on my consumer. Since I will not be using the same technique to consume a service this time around, I’ll need to create my own copies of the service and data contracts. By placing them in their own assembly I’ll be able to reuse this assembly by both my service and my consumer.
Splitting up your contracts into their own assemblies does not mean you should put every contract in a different assembly, nor does it mean separating service contracts from data contracts. Use logical judgment to decide how the breakaway should happen. Unless I find reason not to, I keep service contracts and their associated data contracts in the same project. I really see no reason to split these up. The decision on whether to put all my contracts in one assembly or have multiple assemblies is based on some kind of logical grouping for my contracts. Such groupings may be determined by the services that will be using my contracts. I may have an Order service and a Product service in my design. These two are very specific to the business solution I am designing so their contracts will be grouped together in a single assembly, as would the services as I’ll soon explain. Another service in my system deals with obtaining geographical information. This service will be used in my application but can certainly benefit other applications so I chose to place the data and service contracts associated with it in its own assembly. Security is another reason to consider splitting an assembly. Service contracts will each have their own endpoint. One service can certainly implement multiple service contracts thus having multiple endpoints. Perhaps one endpoint will be used by the Intranet for private access and another by the Internet for public access. I may chose to separate these contracts up so that later I can create multiple proxy assemblies that different applications will use. One application may consume both the ‘private’ and ‘public’ contract, and another only the ‘public’ one. Separating these assemblies ensures that a project does not have access to things it does not need or should not access. I’ll go over the details of the contract implementation and service consumption later.
The next pieces of my design I want in their own assembly are the services themselves. I’ll split these up into one or more assemblies based on the categorization I mentioned earlier. At the very least, I’ll have one assembly for all my services, but chances are I’ll have several depending on how many services I have and their potential for inter-project reusability. Service projects will reference one or more of the contract assemblies I described previously. Also, depending on what these services do, there may be one or more business engines behind the scenes performing business logic or object-relational-mapping. From this level and down to my database, there would be a similarity to a conventional business layer/data layer separation of concerns. You’ll notice then that the service layer acts as a procedural entry point into what can be an elaborate object model, proving that OOP did not die because of SOA.
So far, this assembly separation provides the groundwork for some good reusability scenarios and promotes some good decoupling. My service projects reference the contracts they need only and the services implement only those referenced contracts. There is nothing in my project that I will not need. This leads me to the next assembly.
The service host should also be in its own assembly. Decoupling the services from the host allows you to host your services in whatever type of host you want and to change that host out any time you want. Your host can be an IIS application, Windows Activation Services, or any self-hosting application including console applications, Windows Forms applications, Windows Services, etc. Your decision process as to what kind of host you will have and what bindings will be used should not have anything to do with the actual services themselves; meaning a host should be able to just reference service and contract assemblies and host them with little or no coupling. The only exception to my last statement would be in an MSMQ scenario, but such a scenario is outside the scope of this article. Hosts will have the service configuration information in either a web.config file or an app.config file, and this configuration should be only what you need to expose your service the way you want and with the specifications you want. At a minimum, the configuration information I’ll need are <service> entries with one endpoint per contract.
Now that my assembly separation is designed for my services, I’ll focus my attention to my potential service clients, or consumers.
I’ll use the “Add Service Reference” feature in Visual Studio. This is the largest culprit for anti-reusability and configuration bloat. Not only that, I don’t even want to address my services directly from my clients. As I explained earlier, service consumers use a proxy class in order to access a service’s implementation. This proxy class is not difficult to write and, in fact, attacking this manually gives you a couple of different choices. I’m working here under the assumption that both sides of my application are .NET of course. Should I have a case where I have a Java client or some other type of .NET consumer, the creation of the proxy and the parsing of the service’s message stream would be much different and would use technology specific to that consumer.
A .NET service consumer would not only need a proxy class but also service contracts that represent the messages that are being used. If you recall, I had decided to branch off my service and data contracts into their own assembly, and now I can reuse this assembly by simply referencing it from my service consumer. The service consumer which will contain the service proxies will also become its own assembly. This gives me the ability to use these proxies from more than one client application. Remember that a client application does not necessarily have to be a UI application, but can be an unmanned process as well. Having my service client proxies in their own assembly can be analogous to branching out the business layer of a conventional object-oriented architecture in order to make it accessible by a number of client tiers. In the later examples, I’ll go into detail about how to create proxies and the choices available to you.
The last set of assemblies I will have are simply the clients of my service proxies, which would of course be my UI applications or unmanned processes that want to use what my services have to offer.