Doing tiers with WCF

Published 16 May 8 2:48 PM | William

Because the nomenclature of WCF is often different then the technologies it consolidated, one of the problems you'll likely encounter when moving to it is figuring out how to do the same things you were doing before.  That's probably not the right way to phrase it, what i mean is, how do you implement the WCF equivalent?

With plain old web services, there's more that's similar than different so it's not a big challenge. But architecturally, things aren't as similar as they may appear. And if you try to create a similar architecture for Remoting (you know, that technology Counterfeit ASP.NET Gurus cower away from b/c it's over their head) or MSMQ, things are more pronounced, at least they seem to be.

At the risk of sounding like a cheerleader for the new latest greatest technology from MS, I will say that once you get into WCF, you'd be hard pressed not to find it much simpler and intuitive than any of the older technologies.  I personally have a pretty big investment in Remoting, WS and WSE 2.0 and 3.0.  Letting go of all of that and starting new is a little daunting. After all, while I know I can get there in WCF, it was hard at first coming up with time estimates and even harder making sure that what I was doing was the 'same' as the Remoting or WSE equivalent. You'll likely find yourself in the same scenario but if you just stick it out for a little while, I can promise you'll find yourself astounded at how much easier it is to do things.

I worked on a very large application for the state of South Carolina a few years ago. There was an ASP.NET Front end.  From there, it would call out to a Application Server which hosted the Business Layer. That Business layer in turn called a generic DAL. The DAL in turn called a provider specific DAL.  The provider specific DAl would then call the database.  There was also another facade implemented via WS that used DIME and a few other WSE specific features (by virtue of saying it used DIME, you can tell it was a while ago b/c MTOM has been where it's at for a while now).

All of the layers could be run on one machine or, you could move them out as needed. For scalability this was a great infrastructure. And it helped greatly with security.  Many of the apps that were built for the state were implemented with an ASP.NET Front end that talked directly to the database.  This forced the IT department to make many concessions with respect to the firewall and DMZ and if performance started suffering, the only solution was buying more server resources or getting a new server.  Well, that debate has been had and is over - for large scale applications that need to scale, the front end -> database model is lame.

Trying to implement this n-tier approach in WCF was so easy though it was really amazing.  I simply created a WCF Service library for each tier.  There was a facade which is what the asp.net front end of Winforms front end would communicate with.  This was a service itself which in turn calls out to the Business tier/service.  The Business tier/service is another WCF Service library which calls a Data Tier/service.  That tier is another WCF Service library which can call out to any of the provider specific tiers. Each of those in turn can talk to the database directly. So, you can easily run this application in a manner that means the web server hosting the ASP.NET UI never talks to the db directly.  You can however, by simply changing a few configuration section values, run everything on the same machine (if you wanted).

WCF allows you to run all of the services InProcess (Juval Lowy has an excellent discussion of this in his Programming WCF Services book - which is as good of a book as you'll find).  If they will all be run on the same machine, you can simply used the netNamedPipeBinding

So using the example I mentioned above, the Facade service(s) serves as the Server for the Winforms client or ASP.NET front  end. At the same time though, it is a client of the Business Tier (and you certainly could make this the business tier if you wanted to). I discuss handling it as a client Here.  So the wcf library holding the business logic becomes a server to the facade service, but serves as a client to the generic data tier.  The generic data tier is a server to the business service, but is a client of the provider specific DAL.  The provider specific DAL's are not clients to anything in the WCF sense, but technically they are for they call out to the db server.

B/c many of these tiers simply call other tiers, the interface/contract they use is the same. It could be different if you had reason for it to be, but by using the same contract, you can greatly simplify things. In this instance, I created a library which was shared with every single library, that hosted the contracts.  Looking at the code snippet from my previous article, you can see that the Interface implemented is the same throughout:

The article I pulled this from it available here

ChannelFactory<IStripper> stripperFactory = new ChannelFactory<IStripper>("NamedPipe");
IStripper serviceInstance = stripperFactory.CreateChannel();
using (serviceInstance as IDisposable)
{
   Console.WriteLine(serviceInstance.Bounce(BounceLevel.Woot));
   Console.WriteLine(serviceInstance.SlideDownPoll(PoleManeuver.LookMaNoHands));
   Console.WriteLine(serviceInstance.MilkSuckerForCash("Peter Griffin"));
}

Or where I was using the client proxy...

public partial class PurePlatinumServiceClient : ClientBase<IStripper>, IStripper
    {...}

So using that same methodology, the client configuration file looks like this (I'm leaving out everything unrelated to WCF):

<system.serviceModel>
   <client>
     <endpoint address="net.pipe://localhost/StripperService" binding="netNamedPipeBinding"
         bindingConfiguration="" contract="Cuckooz.Sample.WCFStripClub.IStripper"
         name="MainPipe" />
   </client>

 

No server configuration is needed b/c this is the client tier hence, it does no serving at all.  Now, look at the facade's configuration (again, the code is identical at each tier for the most part. Each tier may do some of it's own validation , but at the end of it, it's using the return base.Channel.MethodName(Params) if I use the clientproxy or calling the method directly if I use the interface.

Now, let's look at the configuration of the Services.  For the actual business implementation, I called each service a slightly different named, like ProductBusinessAPIService, ProductGenericDALService, ProductDALSpecificService and the like.  The interface remains the same in each case, only the name of the service that I'm exposing it as changes.  If I used different interfaces, then obviously those would need to change in the configuration, but little more than that would need to be changed (actually, nothing more). The thing to notice is that other than the client and the data service, each of them is functioning as both a client and a server.  They are clients to the next service in the chain and a server to the one before it. So the facade is a server to the ui client app, but a client of the business service. The business service is a client of the generic DAL but a server to the facade. The generic DALis a server to the business server but a client of the specific DAL. 

If you saw that say the specific DAL was eating a ton of resources, you could simply move it to another box. You'd need to change the bindings to a different mechanism b/c namedpipes won't work across machines but you'd simply need to add a Endpoint that uses the NetTcpBinding for instance, and other than the address, they'd look virtually identical to the existing named pipe equivalent. So without changing a line of code, you could move each piece to a different box (unless you consider config values 'a line of code'. 

So here's the Facade Service:

<system.serviceModel>
    <client>
      <endpoint address="net.pipe://localhost/StripClubBusinessService" binding="netNamedPipeBinding"
        bindingConfiguration="" contract="Cuckooz.Sample.WCFStripClub.IStripper"
        name="ClientPipe" />
    </client>
    <services>
      <service behaviorConfiguration="Cuckooz.Sample.WCFStripClub.StripServiceBehavior"
        name="Cuckooz.Sample.WCFStripClubs.StripperService">
        <endpoint address="mex" binding="mexHttpBinding" name="MainMex"
          contract="IMetadataExchange" />
        <endpoint address="net.pipe://localhost/StripperService" binding="netNamedPipeBinding"
          bindingConfiguration="" name="MainPipe" contract="Cuckooz.Sample.WCFStripClub.IStripper" />
        <host>
          <baseAddresses>
            <add baseAddress="
http://localhost:8731/blahblah/facade/GenericService/" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>

    
<serviceBehaviors>
        <behavior name="Cuckooz.Sample.WCFStripClub.StripServiceBehavior">
        
<serviceMetadata httpGetEnabled="True" />
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>

      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

 

So here's the BusinessService:

<system.serviceModel>
    <client>
      <endpoint address="net.pipe://localhost/StripClubDataService" binding="netNamedPipeBinding"
        bindingConfiguration="" contract="Cuckooz.Sample.WCFStripClub.IStripper"
        name="DataPipe" />
    </client>
    <services>
      <service behaviorConfiguration="Cuckooz.Sample.WCFStripClub.StripperServiceBehavior"
        name="Ger911.HCStandard.Core.API.StripClubBusinessService">
        <endpoint address="mex" binding="mexHttpBinding" name="Mex" contract="IMetadataExchange" />
        <endpoint address="net.pipe://localhost/StripClubBusinessService" binding="netNamedPipeBinding"
          bindingConfiguration="" name="MainPipe" contract="Cuckooz.Sample.WCFStripClub.IStripper" />
        <host>
          <baseAddresses>
            <add baseAddress="
http://localhost:8731/blahblahbalh/CuckoozStripClub/StripperService/" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="Cuckooz.Sample.WCFStripClub.StripperServiceBehavior">

       <serviceMetadata httpGetEnabled="True" />
        <serviceDebug includeExceptionDetailInFaults="True" />
      </behavior>

</serviceBehaviors>
    </behaviors>
  </system.serviceModel>

Now here's the Generic Data Service:

 

<system.serviceModel>
  <services>
    <service behaviorConfiguration="Cuckooz.Sample.WCFStripClub.StripClubDataServiceBehavior" name="Cuckooz.Sample.WCFStripClub.StripClubDataService">
      <endpoint address="mex" binding="mexHttpBinding" name="Mex" contract="IMetadataExchange" />
      <endpoint address="net.pipe://localhost/StripClubDataService" binding="netNamedPipeBinding" bindingConfiguration="" name="DataServicePipe" contract="Cuckooz.Sample.WCFStripClub.IStripper" />
      <host>
        <baseAddresses>
          <add baseAddress="
http://localhost:8731/blahblah/CuckoozGenericDal/GenericDalService/" />
        </baseAddresses>
      </host>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="Cuckooz.Sample.WCFStripClub.StripClubDataServiceBehavior">
                 <serviceMetadata httpGetEnabled="True" />
            <serviceDebug includeExceptionDetailInFaults="True" />

      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

 

Finally, here's the configuration for one of the provider specific (Sql Server) Data Service:

<system.serviceModel>
  <services>
    <service behaviorConfiguration="Cuckooz.Sample.WCFStripClub.StripClubSqlServerServiceBehavior" name="Cuckooz.Sample.WCFStripClub.StripClubSqlServerService">
      <endpoint address="mex" binding="mexHttpBinding" name="Mex" contract="IMetadataExchange" />
      <endpoint address="net.pipe://localhost/StripClubSqlServerService" binding="netNamedPipeBinding" bindingConfiguration="" name="DataServicePipe" contract="Cuckooz.Sample.WCFStripClub.IStripper" />
      <host>
        <baseAddresses>
          <add baseAddress=
http://localhost:8731/blahblah/CuckoozGenericDal/SqlServerDalService/ />
        </baseAddresses>
      </host>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="Cuckooz.Sample.WCFStripClub.StripClubSqlServerServiceBehavior">
        <serviceMetadata httpGetEnabled="True" />
        <serviceDebug includeExceptionDetailInFaults="True" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

---------------------------------------------------------------

In this case, I switched something I was doing in the real world to make it a little more entertaining (and thereby not divulging specifics of a client project).  The real application is a little more complex and includes some other services which are employed as well by each layer - for instance, a crypto service and one to serve up images and documents.  The Generic DAL service is one that we could probably easily do without, but the goal is that if the client wanted to change their DB to Oracle or something else, would could simply develop the service, deploy it, and add endpoints pointing to it.  In that case, we could change the whole mechanism without changing any code or causing any visible interruptions other than the initial switchover which would be miniscule.  By using an example where there are so many moving pieces, it really brings home home much flexibility you have using WCF and how easy it is to implement. Most applications one works on won't have anywhere near the demands for scalability that are in place here (and there are a few other things that dictated this architecture which I don't need to go into). The point is that if you only had one or two service tiers, it would be all that much easier.  Just configure each piece as either a client, a server or both. If you use a shared assembly to hold your client proxies and/or your interfaces (in most cases, I've found this to be beneficial b/c the size of these classes are typically pretty small, so you can get consistency without having to really worry about bloating the footprint by sharing the assembly over and over. And besides, you needed a contract anyway, so there'd only be bloat if you had additional items that you weren't using. in this instance, I'm not introducing anything that I wouldn't have needed anyway).

Oh yah, one other thing... I have a little pet peeve about string literals. They drive me nuts, especially in cases where they are used for session variables.  I'd highly recommend creating a constants class in your shared assemblies library.  For each Endpoint you plan on needing, create a string constant that you'll use as the name of the endpoint. I started out calling it "... Pipe" but realized that was misleading b/c in many cases, I'd be using Tcp instead of Named pipes, so I just moved to something a little more generic.  The naming isn't what really matters, it's the consistency, but having clear and intuitive names doesn't hurt ;-). So doing something old school like this (think back to session variables in ASP.NET):

 

public static class ConfigurationConstants
{
    public const String Localhost = "localhost";
    public const String NamedPipeEndpointName = "MainPipe";
    public const String ClientBusinessServiceEndpoint = "ClientBusinessPipe";
    public const String ServerBusinessServiceEndpoint = "ServerBusinessPipe";
    public const String ClientGenericDataServiceEndpoint = "ClientGenericDataPipe";
    public const string ServerGenericDataServiceEndpoint = "ServerGenericDataPipe";
}

 

I'm trying to make this into a real demo app that's documented and can be done in steps - basically trying to turn it into a full walk-through. So I'm documenting it as I go along. I've gotten it completed through here - but I'm going to add Security, support for MSMQ and a few others (just to show you don't need to change the code you write in most cases) and use the Enterprise library for logging, security and data - if you want the code for this part up through here, just email me privately. I should have the whole thing done in a few weeks. So be advised, i can give you the code, but I don't have much done in the way of explanations in the code - although I will shortly.

---------------------------

Also, if you're interested in WCF, times are really good.  I've read every book except for Michelle's (and that's next on the list b/c I'm sure it's going to be great) and I have to say, they are all good.  It's really a case of "you can't go wrong" whichever one you buy. I'd recommend all of the following - in no particular order:

Programming WCF - Juval Lowy

Essential Windows Communication Foundation (WCF): For .NET Framework 3.5

Inside Windows Communication Foundation

Pro WCF - Practical Microsoft SOA Implementations

Windows Communication Foundation Step by Step

Professional WCF Programming

Windows Communication Foundation Unleashed

Comments

# causticPhil said on May 20, 2008 8:54 AM:

hey bud... i'm glad to see somebody is doing justice to all those hardworking strippers out there...i mean - if it wasn't for those strippers, how else would IT guys ever manage to finally see a naked woman???

Search

This Blog

Tags

Community

Archives

News

  • William G Ryan William Ryan Bill Ryan W.G. Ryan Charles Mark Carroll Charles M Carroll
    My Blog Juice Microsoft MVP
    Bill Ryan W.G. Ryan William Ryan
    Cuckooz' MySpace Page View Bill Ryan's profile on LinkedIn
    My Profile on Twitter
    Please note that this is my personal blog and the opinions expressed are my own. Also, comment moderation is about one of the least important things in my life so please keep that in mind. I can't vouch for the authenticity of any of the posters so please don't hold me accountable. And whatever you do, don't pretend to be Noted Option Strict Off expert and AspFriend Charles Mark Carroll when you post. Doing so will lead him to become apoplectic and write absurd accusatory posts about me that are as coherent and thought out as they are factually correct. He does a stellar job proving his reputation is well deserved and he doesn't need any help from you making himself look foolish. If I have to listen to him banging his spoon off of his high chair one more time, I'm going to burst into flames so please don't make that happen!

    My other sites

    Cool Stuff

    Book Stuff

    Security

    ORM

    Data Access

    Funny Stuff

    Compact Framework Stuff

    Web Casts

    My KnowledgeBase Articles

    My MVP Profile

    Design Patterns

    Performance

    Debugging

    Remoting

    My Fellow Authors

    My Books

    LINQ

    Misc

    Speech

    Syndication

    Email Notifications