Engineering Blog

Specifications 3: The DSL Strikes Back

tl;dr

You should consider building a domain-specific language for certain parts of your code. You can end up with very clear code that can be read by non-programmers.

Designing a domain-specific language

In the last post, we developed a few classes that allowed us to combine small, atomic specifications into more complex ones. The resulting code was very terse, but not particularly meaningful to a non-programmer. Our design is DRY (Don’t Repeat Yourself) but not DAMP (Descriptive And Meaningful Phrases).

Let’s remedy that and build a domain-specific language that resembles English. The end result is going to look like this:

var renameDocumentRule =
  (new UserHasReadPermission().And(new UserCreatedTheDocument())
                              .AndNot(new DocumentIsLocked()))
  .Or(new UserIsWorkspaceManager());

The DSL uses method chaining (sometimes known as a fluent interface) to drastically increase the readability of the code; while the example I started with in the first post had no obvious bugs, this version has obviously no bugs. It’s a huge difference!

We’re going to set ourselves the goal of building this DSL on top of the specifications library we developed in the last post, without changing any of the code we’ve already written.

Extension methods

Me when I use Java

We want to write And and Or as infix methods because that’s how English works, but ISpecification doesn’t contain methods of that name. In Java (the horror!), you’d need to convert the interface to an abstract base class and implement And and Or there. That would pollute our beautiful, crisp interface with a bunch of extra methods for combining specifications. Combining specifications has nothing to do with being a specification, so And and Or don’t belong in an abstract base class. What’s more, a class can only have one parent class (though it can implement many interfaces), so requiring specifications to inherit from an abstract base class is restrictive.

Luckily, we can use C#’s extension methods (indicated by the this keyword) to surgically implant new methods onto our interface from the outside, without changing everything that implements it.

static ISpecification<T> And<T>(
    this ISpecification<T> left,
    ISpecification<T> right)
{
  return new AndSpecification<T>(left, right);
}
static ISpecification<T> Or<T>(
    this ISpecification<T> left,
    ISpecification<T> right)
{
  return new OrSpecification<T>(left, right);
}

These methods operate on an ISpecification<T> and return another ISpecification<T>. This means you can call And on the result of another call to And, chaining the methods indefinitely. Incidentally, this is how LINQ works - methods like Select and Where are defined as extension methods which take an IEnumerable and return a new IEnumerable, allowing you to chain them to one another.

Prefix functions

To make our DSL read like English, we want to implement a prefix function called Not. But C#’s scoping rules present a sticking point - if Not isn’t defined in the current class, we can’t use its name unqualified.

We could bring Not into scope by defining it in a base class which all users of our DSL have to inherit from, but that would be stretching the meaning of inheritance, and has the annoying restriction that our users can’t inherit from any other classes. If we’re willing to give up on the idea of a prefix function, a more sanitary option is to define new extension methods which negate their argument:

static ISpecification<T> AndNot<T>(
    this ISpecification<T> left,
    ISpecification<T> right)
{
  return new AndSpecification<T>(
      left,
      new NotSpecification<T>(right));
}
static ISpecification<T> OrNot<T>(
    this ISpecification<T> left,
    ISpecification<T> right)
{
  return new OrSpecification<T>(
      left,
      new NotSpecification<T>(right));
}

But extension methods need an instance of ISpecification to chain from. What if the specification you want to negate isn’t part of a method chain? The best we can do is write a static method with the least disruptive name we can think of:

public static class Not
{
  public ISpecification<T> This<T>(ISpecification<T> spec)
  {
    return new NotSpecification<T>(spec);
  }
}

// example
var documentIsNotLocked = Not.This(new DocumentIsLocked());

Users still have the option of defining a local function called Not as an alias for Not.This. C# 6’s upcoming “using static” feature would solve this problem entirely - I could define a class containing a static Not method and users of the DSL would be able to import the name directly.

When to use a DSL

We’ve defined a simple, readable domain-specific language for composing specifications using C#. When does it make sense to build a fluent DSL?

In the next post, we’ll enterprise-ify this code even more, to make it easier to add new ways to evaluate specifications.

In this series

  1. All about security
  2. The power of Composite Specifications
  3. Specifications 3: The DSL Strikes Back
  4. Knock knock. Who’s there? AbstractSpecificationNodeVisitorImpl
comments powered by Disqus