Wednesday, September 12, 2007

Making perfect software in a non-perfect world

Ideally, every software project would be lavishly described and work in his own little world, having a dedicated database and no dependencies that slow you down. In such conditions, you can just sit yourself down, and work uninterrupted, quickly reaching Programming Nirvana.

Of course, no real project is ever like that, so the trick then becomes how do we work in an environment where your data is spread out over several applications, and these applications each have their own timetable and their own priorities. One of the possible techniques is called Stubbing.

Stubbing or Mocking is a technique that is much better described by the likes of Martin Fowler, and other high-profile architects but in short, its a way of first hiding away the implementation of a complex subsystem by only exposing the interface to those applications that will be using it and then code against a simplified (or dummy) implementation of that interface, while the 'real' application is being made.
This allows you to continue with the rest of your app, and come back to the subsystem
when you have successfully driven your users into a corner and forced them to do their work.

The concept of Stubbing seems to scare a lot of developers, because on the surface it seems to be something for huge projects and - lets face it - is often explained in needlessly complex terms. This is unfortunate because even in smaller projects, stubbing can be a very useful technique and doesn't have to be extremely complicated.

An example to illustrate.

You're tasked to implement a medium-sized SOA project, but you need to get some data from your local AS400 system. Unfortunately, the AS400 guy has just fallen off his roof and will be out for the next two weeks. So instead of twiddling your thumbs for two weeks, Stubbing will allow you to implement the other tasks at least and leave the implementation of the AS400 subsystem for later.

So what we need is essentially a system that is going to pretend to be an As400 and that allows us to "plug" this in dynamically (meaning, when the actual AS400 application becomes available, we don't have to change much code - preferably none!).

To implement this, we'll start by creating an Interface. Learn to love those, they are your best friend.

public interface IAs400Connector
{
PORT GetLines(string user, string from, string to);
}


Next step, create our 'stub' class and implement this interface.

public class StubAs400Connector : IAs400Connector
{
public PORT GetLines(string user, string from, string to)
{
string fileName = "Sample.xml";
string xml;
using (StreamReader streamReader = new StreamReader(fileName))
{
xml = streamReader.ReadToEnd();
}

return XmlPortSerializer.DeSerialize(xml);
}
}


When creating a stub class, its always a good idea to structure your stub data as close as you can to the original data. That way, you'll limit the work you have to do when you plug in the real class later on. In this particular instance, we assume that the AS400 developers can give us a DTD or a Schema beforehand, so we can easily generate some sample data which should be very similar to the real thing (in theory). Implementation then simply consists of reading the XML sample file, and deserializing it into a custom object (which has been previously generated by a tool such as XSD.exe).

The hard? part is mostly done now, all that remains is to implement a little Factory, so that the calling application doesn't have to worry about instantiating the right Connector. In fact, we'll go one step further and push that decision right to the configuration file.

public static class As400ConnectorFactory
{
private static IAs400Connector connector;

public static IAs400Connector GetAs400Connector()
{
if (connector == null)
connector = (IAs400Connector)Activator.CreateInstance(Type.GetType(Settings.Default.As400Connector));

return connector;
}
}


There's a few things going on here. First of all, we make use of the Properties feature of .NET 2.0, in which you can include dynamic,
strongly-typed properties in your project and have those populated by the calling application through means of a configuration file, as seen below. These settings can then be called in your application, through an (autogenerated) strongly-typed Settings class.



<configsections>
<sectiongroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="Project.As400Connector.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirepermission="false">
</section>
</sectiongroup>

<applicationsettings>
<project.as400connector.properties.settings>
<setting name="As400Connector" serializeas="String">
<value>Project.As400Connector.StubAs400Connector, Project.As400Connector
</setting>
</project.as400connector.properties.settings>
</applicationsettings></configsections>




Second, we then use Reflection to instantiate the Connector specified in the configuration file.

connector = (IAs400Connector)Activator.CreateInstance(Type.GetType(Settings.Default.As400Connector));


If you're worried about using Reflection to create your class on every request, just make the connector static like I did, that way it'll get cached. Keep in mind that you'll need to reset the application then, if you want to force a change in connector. Of course, if you use this in a web context, changing the Web.Config will automatically reset your application anyway, so its a moot point :)

Now, you can merrily start designing your screens and grids and lists and whatnots, without stressing about the lack of data. Once your AS400 boys then finish their work, you code the 'real' Connector class, switch to it in your configuration file and test with the real data. If everyone has done their homework, the work left should then be minimal (mostly validating the length of the real data and taking out some inevitable quirks).

While this is obviously a very simple example, in larger projects nothing is stopping you from implementing a much more complex stubbing system, if needed.

No comments: