Chad's Blog

The Specification Pattern

Posted by Chad Hendry on 01 Feb 2008

A long while ago I was afforded the pleasure of working on a .Net WinForms call center application for a large company. I’ll call them Acme Finance. The application consisted of fifty or so screens: a screen to verify who was calling, one to show recent transactions, one for payment history, etc.

The problem was that Acme kept buying other companies, and each company (they called them business segments) required extensive customization of the screens. The code became littered with conditional logic for adding, removing, and tweaking features based on the business segment to which the logged-in CSR belonged.

Eventually it grew to the point that the application wasn’t cost effective to maintain and had to be rewritten. Having learned that so much conditional logic isn’t maintainable, Acme decided that each business segment should get their own copy of the screens. The problems created by duplication were far worse than those caused by the conditional logic, but that’s another post.

One of the more ridiculous aspects of the new code base was the mechanism by which the application decided which set of screens to load. There were about seven parameters to this decision, each one having three or four possible values. Some of the more significant parameters were:

  • Which business segment is this call center serving?
  • What is CSR’s role?
  • Which environment are we in?
  • What type of customer is on the phone?

To figure out which screens to load, all seven parameters were joined together to form a key into the configuration file. These keys mapped each combination of parameter values to one of about thirty or so “navigation XML files,” each containing a list of screens. Thus we had thousands of lines that all looked very similar to this:

<add key="Acme.Manager.Production.Silver.This.That.Other" value="NavData_026.xml" />

This mapping was hard to make any sense out of, was difficult to maintain, and became a huge source of bugs. When a bug was determined to be caused by a bad mapping, often only that mapping was fixed and the bug would show up again in a slightly diferent context, perhaps when it was a Gold customer calling instead of a Silver customer.

One solution to this problem is to give each navigation XML file a specification of conditions that must be met in order for it to be selected. For instance, you might have something like this:

<NavigationXml filename="NavData_PreferentitalService.xml">
  <Or>
    <CustomerType>Platinum</CustomerType>
    <And>
      <Or>
        <CSRType>Admin</CSRType>
        <CSRType>Manager</CSRType>
      </Or>
      <Not>
        <BusinessSegment>GrouchyBiz</BusinessSegment>
      </Not>
    </And>
  </Or>
</NavigationXml>

This states that the preferential service navigation XML file is to be used when the customer is “platinum level” or if the CSR is an administrator or manager. However, preferential service is never available to those calling GrouchyBiz.

The implementation is straightforward. Say that the context of the call is stored in an instance of the CallingContext class:

class CallingContext
{
  public string CustomerType;
  public string CSRType;
  public string BusinessSegment;
}

We’ll start by defining an interface for evaluating whether or not a CallingContext matches a specification:

interface ICallingContextSpec
{
  bool Matches(CallingContext c)
}

Next we build ICallingContextSpec implementations for matching specific CustomerType, CSRType, and BusinessSegment values. For example we might have something like this:

class CustomerTypeSpec : ICallingContextSpec
{
  private string _customerType;

  public CustomerTypeSpec(string customerType) {
    _customerType = customerType;
  }

  public bool Matches(CallingContext c) {
    return c.CustomerType == _customerType;
  }
}

This approach really starts to shine when we add a few classes that allow us to specify and, or, and not expressions:

class AndSpec : ICallingContextSpec
{
  private ICallingContextSpec[] _specs;

  public AndSpec(params ICallingContextSpec[] specs) {
    _specs = specs;
  }

  public bool Matches(CallingContext c) {
    foreach (ICallingContextSpec spec in _specs) {
      if (!spec.Matches(c))
        return false;
    }

    return true;
  }
}
    
class OrSpec : ICallingContextSpec
{
  private ICallingContextSpec[] _specs;

  public OrSpec(params ICallingContextSpec[] specs) {
    _specs = specs;
  }
    
  public bool Matches(CallingContext c) {
    foreach (ICallingContextSpec spec in _specs) {
      if (spec.Matches(c))
        return true;
    }
    
    return false;
  }
}
    
class NotSpec : ICallingContextSpec
{
  private ICallingContextSpec _spec;

  public NotSpec(ICallingContextSpec spec) {
    _spec = spec;
  }
    
  public bool Matches(CallingContext c) {
    return !_spec.Matches(c);
  }
}

Now we can compose objects to build specifications. For instance, the XML example above would be built like this:

ICallingContextSpec preferentialSpec = new OrSpec(
  new CustomerTypeSpec("Platinum"),
  new AndSpec(
    new OrSpec(
      new CSRTypeSpec("Admin"),
      new CSRTypeSpec("Manager")
      ),
    new NotSpec(
      new BusinessSegmentSpec("GrouchyBiz")
      )
    )
  );

if (preferentialSpec.Matches(currentCallingContext)) {
    // preferential treatment available
}

Of course we need to be able to load these specifications from XML, and that too is simple. Instead of using the constructor to configure the individual specification objects, we’ll add a LoadFromXml method to the ICallingContextSpec interface:

interface ICallingContextSpec
{
  void LoadFromXml(XmlNode node);
  bool Matches(CallingContext c);
}

Then we’ll introduce a factory for instantiating the correct ICallingContextSpec based on the XML node’s name:

class SpecFactory
{
  public static ICallingContextSpec FromXml(XmlNode node) {
    ICallingContextSpec spec = (ICallingContextSpec)
      Activator.CreateInstance(Type.GetType(node.Name + "Spec"));
    spec.LoadFromXml(node);
    return spec;
  }
}

With this in place, our ICallingContextSpec classes look like these:

class CSRTypeSpec : ICallingContextSpec
{
  private string _csrType;

  public void LoadFromXml(XmlNode node) {
    _csrType = node.InnerText;
  }

  public bool Matches(CallingContext c) {
    return c.CSRType == _csrType;
  }
}

class OrSpec : ICallingContextSpec
{
  private List<ICallingContextSpec> _specs;

  public void LoadFromXml(XmlNode node) {
    _specs = new List<ICallingContextSpec>();
    foreach (XmlNode child in node.ChildNodes)
      _specs.Add(SpecFactory.FromXml(child));
  }

  public bool Matches(CallingContext c) {
    foreach (ICallingContextSpec spec in _specs) {
      if (spec.Matches(c))
        return true;
    }

    return false;
  }
} 

Avoiding Brittle Configuration

Posted by Chad Hendry on 22 Jan 2008

Solving problems by creating configuration-driven, generic frameworks is usually a bad idea. But even when configurability does bring real value, it’s often done in a way that breaks down too quickly when requirements change.

Configuration can often be as simple as storing flags for enabling features, connection strings for databases, and URLs for web services. But sometimes flags, strings and numbers just aren’t rich enough.

Take for example formatting a SQL result set into flat file records. Let’s start by configuring each field with a width. Then we need an alignment. Then a padding character. What if it’s a date? We’ll need a date format. If it’s a number, does it have an implicit decimal point at some position?

Data-driven configuration breaks down when we try to account for all of these possibilities. Requirements will change in unforeseen ways and demand functionality that the configuration parameters are unable to express. Perhaps we have to format some number in hex. Other requirements are even more difficult to anticipate—-what if we need to calculate and append a check digit?

A different approach is to make the framework extensible. Rather than relying on data-driven configuration and conditional logic to interpret it, let’s introduce an interface:

public interface IFieldFormatter
{
  void Initialize(XmlNode fieldNode);
  string Format(object value, int width);
}

Now when an application using our framework has a unique requirement, such as being able to display a date and time adjusted to a configurable timezone, it can implement a new IFieldFormatter and reference it from the configuration file. A hypothetical configuration file might look like this:

<fields>
  <field width="17" name="OrderTime"
      type="Fields.Formatting.DateFormat,Fields.Formatting">
    <TimeZone>CST</TimeZone>
  </field>
  <field width="10" name="OrderAmount"
      type="Fields.Formatting.DollarFormat,Fields.Formatting" />
</fields>

With this in place, the framework becomes focused on reading the configuration file, building the appropriate IFieldFormatter objects, and using them to produce the output. This brings several advantages such as the ability to add custom formatters without having to risk modifying existing, working code.

This example also demonstrates the characteristic dependency reversal that often accompanies good object-oriented design. In a structured solution, the formatting code might reside in a library about which the processing engine has direct knowledge. In the interface-based approach, however, the formatters depend on the processing engine for its definition of IFieldFormatter, while the engine itself has no knowledge of any concrete formatters. This is a good thing because it’s better to depend on the relatively stable processing engine than the more volatile formatting library.

Always remember that it’s easy to go overboard. Focus on keeping things simple. If a couple flags and a few parameters can solve your problems without much hassle, take that route! Even if you anticipate needing something fancy down the road, it’s usually best to defer implementing it until the moment you actually need it.

Older posts: 1 2 3 4
geico ringtone loop
stop!