In this article, I'm going to walk through a basic Remoting scenario and use it for a more in depth discussion on building a large scale distributed solution. When I first started learning about Remoting I fell upon a few stumbling blocks and pretty much just wrote it off. "I can just use Web Services so why go through the drudgery of Remoting" was the excuse I used to not dig into it. Today, I can hardly imagine programming without it. There are a few gotchas that I encountered along the way that seemed 'really difficult' at the time which in retrospect, were so trivial that I'm hesitant to acknowledge them. But, I've become such an avowed Remoting nut that hopefully I can either talk you into using it or convince you it's not as hard as it looks.
A few years ago when I was in college, Client/Server was the big thing (well, it was more than a few years ago but we don't need to go there). C/S solutions were already fairly common and the Computer Science department was doing its best to prepare us for the world out there. In one of my senior level classes we started playing with an Oracle db. It was heaven compared to the lame text file stuff I was so accustomed to. So I decided that I really liked database programming and started branching out on my own into Oracle Forms and Oracle Reports. At that time, all of the Oracle books I bought were preaching about the virtues of N-Tier vs Client Server architecture. It was pretty intuitive considering that in school we did mostly C++ work. After all, if we decouple Header files from the implementation files, why not use the same metaphor on a larger scale. So there was this notion of a Presentation Layer, A Business Tier and a Data Access Tier. It all made perfect sense. Then I got a job working in VB development and I was stoked to really give this N-Tier thing a try. In my interview the N-Tier thing was dropped about 15 times so I was pretty excited. The manager stressed over and over how important it was to decouple the business logic from the presentation logic and the data access logic from the business logic.
What I encountered however was probably rather typical. "Decoupling" meant building a .dll for the business logic, another one for the data access logic and hard referencing them into a Winforms application. Sure, this was certainly a lot better architecturally than say, putting all the code behind a few Winforms but what did it really buy us? Well, for one thing it meant that we could replace a .dll without disturbing the rest of the application so it afforded us some flexibility. But other than that, it didn't get us much. The client machines STILL NEEDED THOSE DLL"S and the project still had hard coded references to them in the build.
A while later I took another job and things were essentially the same although there were a few hot shots there that were all about DCOM. It looked really cool but since I was only doing VB development for about 2 years at that point the response was "NO DCOM FOR YOU!". Well, that's not entirely true but most of what I got to do was really painful and was essentially following a set of very specific instructions. No real development on my part. To be honest, my first foray into DCOM and DNA was about as anticlimactic as it could have been. Anyone that could type could have done what I did.
So how are things different now? Well, today you have two pretty effective ways of handling your communications layer - Web Services and Remoting. For a while I thought Web Services were easier to use than Remoting but that's not really true. It's true inasmuch as it's easier to get a web service up and running because Remoting configuration is a bit fussy, but after that it's a different story. And web services are great for many things but they aren't an effective mechanism for many things you may need to do. And do you really want to call a web service on an in house program when you can hit the database directly, just so you can have a more scalable solution? Not if performance is a critical factor. This is where Remoting is worth its weight in gold. You see, Web Services rely on Remoting themselves, so by using remoting directly you can cut out a large layer of fat.
Enough background though, let's get started.
In a typical client server solution, you may have a Winforms application, a business layer and a data access layer. These layers are often times just a fancy way of saying a DLL Reference. As such, the business objects are running on the same box as the UI. Anyway, to keep things simple, lets say we have an application called KDNClient and a business layer called KDNBusiness. You add a reference to KDNBusiness to your KDNClient assembly and now you can reference everything in KDNBusiness just like it was any other object.
So how does a remoting scenario differ? Well, there are many permutations but let's go through a simple one. In this instance, we're going to make a very simple console appliction called KDNServer. Now, we're also going to have the same client we otherwise would have, KDNClient. So where does KDNBusiness exist? Well, it's going to exist on the Server. In this particular example I'm going to host them on the same box. KDNBusiness has two methods .GetAssemblyName() and GetCurrentTime(). I know it's a useless class but I wanted to keep this simple because getting started with Remoting is often the most challenging part. In the next article we're going to add a Data Access layer to this, which can run on a different machine than both KDNServer and KDNClient. Now, to facilitate the separation of tiers that I mentioned eariler, I'm going to create another assembly (KDNBusinessInterfaces) with one Interface IBusinessInterface (I know, I violated the naming conventions, my bad). IBusinessInterface defines two methods: GetAssemblyName() and .GetCurrentTime(). Below is a picture of the actual solution I used to build this project which proves that there is NO Reference to KDNBusiness:
Figure 1.1 - References for KDNClient
Now, all I have so far is a client application with a reference to an assembly that contains an interface in it. I'll need somewhere to host the business assemblies though and in this case I'm going to create a console application that does only one thing - calls the static RemotingConfiguration.Configure method passing in the name of the configuration file. Since this is a console application, all you need to do to add the configuration file is select Project -> Add New Item, then select Application Configuration. It will automatically add this for you and when you compile, it will be named AssemblyName.exe.config. As an aside, Configuration files are EXTREMELY Powerful and allow you to do things very simply that would otherwise be a real pain in the butt. You can handle the configuration through code instead of using a .Config file, but in this scenario it would be really shortsighted. I say that because if you wanted to move the DALC layer for instance to another server or change any of the remoting configuration info, add a new type or anything else, you'd need to recompile and redeploy. That is very cumbersome as opposed to merely doing some minor editing in a text editor. To prove this point, I'm actually going to add the information needed for the DALC Layer that I'm going to discuss in the next article. As such, I will be able to add an entire tier, running on another machine, without touching one line of anything in the server application.
Since this is a Console application, I need to add in one line of code (Console.ReadLine()) so that the application stays running. Other than that only one line is needed to make a "Remoting Server" as illustrated below (yes, this code actually works and that's all there is to it):
Figure 1.2 - KDNRemoteServer
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http
Module Module1
Sub Main()
RemotingConfiguration.Configure("KDNRemoteserver.exe.config")
Console.WriteLine("KDN Remoting Server (Running)")
Console.ReadLine()
Console.WriteLine("Closing down server")
End Sub
End Module
Also, note that this part is written in VB.NET. I did this on purpose because the rest of the application is written in C# and I wanted to illustrate that you can easily mix and match languages and still do whatever you need.
I created another assembly in the same solution, KDNBusinessAPI which will hold the Business Object: The entire class definition is provided below. Note that although this is a really simple class, it can get much more complex and in part II of this article, I'll have a much more complex example:
Figure 1.3 - KDNBusinessAPI
| using System; using KDN.BusinessLayer.Business.Interfaces; namespace KDN.BusinessLayer.Business { /// <summary> /// Summary description for Class1. /// public class Business : MarshalByRefObject, IBusinessInterface { static Business(){} public System.String GetAssemblyName() { return System.Reflection.Assembly.GetExecutingAssembly().FullName; } public System.DateTime GetCurrentTime() { return DateTime.Now; } } } |
| <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <channels> <channel ref="http" port="8010" /> channels> <service> <wellknown mode="Singleton" type="KDN.BusinessLayer.Business.Business, KDNBusinessAPI" objectUri="Business.soap"/> <wellknown mode="Singleton" type="KDN.DataAccessLayer.BusinessDALC, KDNBusinessDALC" objectUri="BusinessDALC.soap"/> service> application> system.runtime.remoting> configuration> |
| using System; namespace KDN.BusinessLayer.Business.Interfaces { public interface IBusinessInterface { System.String GetAssemblyName(); System.DateTime GetCurrentTime(); } } |
| private void button1_Click(object sender, System.EventArgs e) { HttpChannel kdnChannel = new HttpChannel(); ChannelServices.RegisterChannel(kdnChannel); object BusinessInstance = Activator.GetObject(typeof(IBusinessInterface), "http://localhost:8010/Business.soap"); IBusinessInterface bus = (IBusinessInterface) BusinessInstance; rtb.Text += "Remote Assembly: " + bus.GetAssemblyName() + "\r\n"; rtb.Text += "Current Assembly: " + System.Reflection.Assembly.GetExecutingAssembly().FullName; rtb.Text += "\r\n " + "Current Time: " + bus.GetCurrentTime().ToString(); } |