I am writing something today that is designed to be a constructive article. It deals with refactoring solutions to get rid of coupling. The problem is one I am dealing with on an application co-written by our company and a vendor. And, due to moving parts, is a pain point right now. The article is written for two reasons:
- Release some of my frustrations, so you can understand why tight coupling is a bad thing
- Constructively look at solutions to the problem domain so we can all get better at what we do
I write this risking sounding like a rant, which is generally a useless exercise. This is not aimed at the issue at hand, in particular. I want that to be clear, as I am not pointing fingers. That is also why I am not naming names, but just getting to the core of the issue.
Problem 1: Tight Coupling
This is the third application I have worked on where a back end solution has been coupled to a front end solution through object libraries. The idea is simple. You start with a back end that has a set of libraries. Rather than serialize to different types, you feel you should share libraries. To communicate between projects, you serialize objects directly from front end to back end with some form of communication code set up in one of the libraries.
The main problem with this set up is you end up coupling the two projects without an explicit coupling. Let me explain.
When you hear the term "tight coupling" it is normally through some hard coded connection between layers (or tiers) of you application. The most common is hard coded bits. Let’s take, for example, the most tightly coupled thing I can think of off hand: the SqlConnection.
string connectionString = "{My connection string}";
SqlConnection connection = new SqlConnection(connectionString);
In that example, you are not only coupled to SQL Server, but you are coupled to a particular instance of SQL Server. Yuck!
So, you move your connection string to the config file, and end up with something like this:
string connectionString =
      ConfigurationManager.ConnectionStrings["MyConnString"].ToString();
You are now decoupled from a particular instance, but your solution is coupled to SQL Server. Now, this might be acceptable in some instances, as your employer may be dead set against any other type of database. In these cases, the solution is acceptable, even if it is not ideal. You can further decouple the solution in a couple of ways. First is through interfaces and a factory method. Please note that the following is "on the fly" pseudocode and not something you can boilerplate:
string connectionString = …
DbType connectionType = …
IConnection connection = ConnectionFactory.GetConnection(connectionString, connectionType);
The factory will then use the connectionType to return a Connection object from the correct type of database. It will be returned as IConnection, an interface, and you will run the objects methods via this interface and not directly to the object methods. What this means is you cannot run extra, database specific methods, without casting (and coupling a bit).
As an example, suppose the Microsoft .NET team created a new XML method that works on SQL Server. As it is only available in SQL Server, they do not have it coded on the interface (this is a stretch, but something that can easily happen in your own objects). So we have something like so:
public sealed class SqlConnection
{
   //Part of IConnection
   public int ExecuteNonQuery();
   //Method only found in SqlConnection
   public DataSet RunXML(string xml);
}
Ignore that the above would never happen, as it is bad form. Focus, instead, on the fact it could happen if you are coding your own classes. Think high level, as well, rather than dwelling on the specific method names.
Here is what happens in your code, to run that method:
IConnection connection = ConnectionFactory.GetConnection(connectionString, connectionType);
SqlConnection conn = (SqlConnection) connection;
DataSet ds = conn.RunXML(xmlString);
Let’s continue. There are, of course, means of making this a bit more generic, but you get the basic idea. We can further decouple by using both interface and a service boundary. This is one of the reasons SOA is more than just a buzzword today. 🙂 Okay, for some, who do not understand, it is still a buzzword.
In this instance, the factory is moved over to the service side and our code calls the service to complete our work. NOTE, please, that I am not suggesting you should change all of your standard calls in your application to service calls. There is a trade off between coupling and performance. There are instances, even on a single machine, where a service call makes sense.
You then move the all of the datacode completely out and end up with something like this:
DataSet ds = service.GetData(xmlString);
The exact method call is unimportant here. Just the fact that we are calling a service. Now, on the service end, it might be doing this:
IConnection connection = ConnectionFactory.GetConnection(connectionString, connectionType);
SqlConnection conn = (SqlConnection) connection;
DataSet ds = conn.RunXML(xmlString);
or perhaps this:
IConnection connection = ConnectionFactory.GetConnection(connectionString, connectionType);
SqlConnection conn = (SqlConnection) connection;
string commandString = GetCommandStringFromXML(xmlString);
What you have actually encapsulated in the service is unimportant as long as you agree that an XML string turns into a DataSet inside the black box.
Problem 2: Generic Interfaces
In the last few paragraphs of the last section, we ended up with a method that seems to work, namely:
[WebMethod]
public DataSet GetData(string xml) {}
But does it really solve the problem? Think about this for a second. What happens if you start with this XML format.
<books>
 <book>
    <id>1</id>
    <name>Moby Dick</name>
  </book>
</books>
But, you later decide to change it to this:
<books>
 <book id="1">
    <name>Moby Dick</name>
  </book>
</books>
The method call does not change, but the XML string does. You are no longer tightly coupled, but you have left yourself in a situation where changing an XML format can blow up the communication between applications and you do not even find out until you run the client.
Okay, so you are saying "I would NEVER do that." Perhaps not, but someone down the road may think that an attribute is more efficient for ID or vise versa that it should be an element, for consistency, and not an attribute. Either of these situations will cause a run time error … or not. In some instances, you may have code that obfuscates the error like so:
XMLDocument doc = new XmlDocument();
try
{
   doc.Load(xmlStream);
}
catch
{
   //Just trying to avoid an error so my boss does not get pissed
}
In this case, the blow up happens on the client when crap is returned. Or worse, it is properly formatted as the correct DataSet, but there are no records. So, everything seems to flow correctly, but you are not getting the correct data.
My preferred method, is to set up signatures that cannot be changed willy nilly. Or, if you do use XML, set it up so it adheres to a specific schema (or DTD). Then you can check the schema and return a proper message if it is wrong. I also recommend that you set up unit tests on all of your methods to ensure you realize when you break an interface. This is especially critical when you go generic with a string type to hide XML, or other similar non-specific constructs.
I fired this at someone and he said "but how can I test my web methods for type"? There are a couple of ways, like nUnitAsp (not actively supported) or something better like treating a web method as a UI element and throwing all of the code into libraries. From this:
[WebMethod]
public DataSet GetData(string xml)
{
   //Do some work with XML here
   return ds;
}
to something like this:
[WebMethod]
public DataSet GetData(string xml)
{
   return ServiceLibrary.ServiceClass.GetData(xml);
}
namespace ServiceLibrary
{
   public class ServiceClass
   {
       public DataSet GetData(string xml)
       {
           //do some work with XML here
           return ds;
       }
   }
}
Don’t get bogged down on the naming here or the fact we have not added the testing against schema, etc. The important concept is your service is a UI "page" and calls a library for the work. Very testable.
Random Thoughts (MVC, separation of concerns, & unit testing)
I decided to add this here, not because it fits, but because I am thinking about it right now. I know that is a bad reason and I should blog elsewhere, but it "kind of" goes with what I have gone through above.
One of the goals of the MVC Framework is testability. If you use a controller, you can test all of your functionality. With a simple "fake", you can even ensure the correct view is instantiated. As this is an aside, no code here.
But, you can accomplish much of the same if you treat your UI as a veneer and do all of the work in libraries. Rather than code like so:
//Event handler for button click
protected void Button1_Click(object sender, EventArgs e)
{
   //Do work here
}
you go to something more like this:
//Event handler for button click
protected void Button1_Click(object sender, EventArgs e)
{
   //pull values
   string one = TextBox1.Text;
   string two = TextBox2.Text;
   bool success = EventHandlingClass.HandleSubmit(one, two);
}
public class EventHandlingClass
{
   public static bool HandleSubmit(string one, string two)
   {
       //Do work here
       return isSuccess;
   }
}
Once again, the names are there to show what kind of work is being done, etc. Do not focus on them; focus on the concept. The more work done in libraries, the more you can test. For me to test, I now create something like so:
[UnitTest]
public void TestHandleSubmit()
{
   string one = "";
   string two = "";
   bool actual = EventHandlingClass.HandleSubmit(one, two);
   Assert.IsTrue(actual, "Value returned was not true");
}
Don’t get bogged down in the test specifics either, as everything in this article is make believe. The takeaway here is you know the method is working, or not working. If you find a bug, add a test to confirm the bug. If the bug cannot be confirmed via a test, you have a UI problem (pulling wrong value?), which is fairly easy to fix, even if you cannot test UI, as you can easily determine which value is causing the problem and quickly find something like this:
string one = TextBox2.Text;
Do you still have to test UI? Certainly. You might automate some of this testing, as well, through a variety of tools (nUnitAsp, Team Test tools, etc.). But, the more code in libraries (even if you are coding facade libraries between UI and business layer), the better.
Too many subjects in one post? Probably.
Peace and Grace,
Greg