Tamarack: Chain of Responsibility Framework for .NET

The Chain of Responsibility is a key building block of extensible software.

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. – Gang of Four

Variations of this pattern are the basis for Servlet Filters, IIS Modules and Handlers and several open source projects I’ve had the opportunity to work with including Sync4J, JAMES, Log4Net, Unity and yes, even Joomla. It’s an essential tool in the OO toolbox and key in transforming rigid procedural code into a composable Domain Specific Language.

I’ve blogged about this pattern before so what’s new this time?

  1. The next filter in the chain is provided via a delegate parameter rather than a property
  2. The project is hosted on github
  3. There is a NuGet package for it

pkgmgr3

How does it work?

It’s pretty simple, there is just one interface to implement and it looks like this:

public interface IFilter<T, TOut>
{
    TOut Execute(T context, Func<T, TOut> executeNext);
}

Basically, you get an input to operate on and a value to return. The executeNext parameter is a delegate for the next filter in the chain. The filters are composed together in a chain which is referred to as a Pipeline in the Tamarack framework. This structure is the essence of the Chain of Responsibility pattern and it facilitates some pretty cool things:

  • Modify the input before the next filter gets it
  • Modify the output of the next filter before returning
  • Short circuit out of the chain by not calling the executeNext delegate

Show me examples!

Consider a block of code to process a blog comment coming from a web-based rich text editor. There are probably several things you’ll want to do before letting the text into your database.

public int Submit(Post post)
{
    var pipeline = new Pipeline<Post, int>()
        .Add(new CanoncalizeHtml())
        .Add(new StripMaliciousTags())
        .Add(new RemoveJavascript())
        .Add(new RewriteProfanity())
        .Add(new GuardAgainstDoublePost())
        .Finally(p => repository.Save(p));
 
    var newId = pipeline.Execute(post);
 
    return newId;
}

What about dependency injection for complex filters? Take a look at this user login pipeline. Notice the generic syntax for adding filters by type. Those filters are built-up using the supplied implementation of System.IServiceProvider. My favorite is UnityServiceProvider.

public bool Login(string username, string password)
{
    var pipeline = new Pipeline<LoginContext, bool>(serviceProvider)
        .Add<WriteLoginAttemptToAuditLog>()
        .Add<LockoutOnConsecutiveFailures>()
        .Add<AuthenticateAgainstLocalStore>()
        .Add<AuthenticateAgainstLdap>()
        .Finally(c => false);
 
    return pipeline.Execute(new LoginContext(username, password));
}
 

Here’s another place you might see the chain of responsibility pattern. Calculating the spam score of a block of text:

public double CalculateSpamScore(string text)
{
    var pipeline = new Pipeline<string, double>()
        .Add<SpamCopBlacklistFilter>()
        .Add<PerspcriptionDrugFilter>()
        .Add<PornographyFilter>()
        .Finally(score => 0);
 
    return pipeline.Execute(text);
}
 

Prefer convention over configuration? Try this instead:

public double CalculateSpamScore(string text)
{
    var pipeline = new Pipeline<string, double>()
        .AddFiltersIn("Tamarack.Example.Pipeline.SpamScorer.Filters")
        .Finally(score => 0);
 
    return pipeline.Execute(text);
}

Let’s look at the IFilter interface in action. In the spam score calculator example, each filter looks for markers in the text and adds to the overall spam score by modifying the result of the next filter before returning.

public class PerspcriptionDrugFilter : IFilter<string, double>
{
    public double Execute(string text, Func<string, double> executeNext)
    {
        var score = executeNext(text);
 
        if (text.Contains("viagra"))
            score += .25;
 
        return score;
    }
}

In the login example, we look for the user in our local user store and if it exists we’ll short-circuit the chain and authenticate the request. Otherwise we’ll let the request continue to the next filter which looks for the user in an LDAP repository.

public class AuthenticateAgainstLocalStore : IFilter<LoginContext, bool>
{
    private readonly IUserRepository repository;
 
    public AuthenticateAgainstLocalStore(IUserRepository repository)
    {
        this.repository = repository;
    }
 
    public bool Execute(LoginContext context, Func<LoginContext, bool> executeNext)
    {
        var user = repository.FindByUsername(context.Username);
 
        if (user != null)
            return user.IsValid(context.Password);
 
        return executeNext(context);
    }
}

Why should I use it?

It’s pretty much my favorite animal. It’s like a lion and a tiger mixed… bred for its skills in magic. – Napoleon Dynamite

It’s simple and mildly opinionated in effort to guide you and your code into The Pit of Success. It’s easy to write single responsibility classes and use inversion of control and composition and convention over configuration and lots of other goodness. Try it out. Tell a friend.

8 Comments  »

  1. tmont says:

    Very cool! I like the anonymous function/continuation approach for executing the next filter in the pipeline.

    Why is “Finally()” required? Couldn’t you just make the tail the last “Add()”ed filter?

  2. Michael Valenty says:

    I suppose it doesn’t need to be required. It could just throw an exception if you call executeNext when you’re on the last filter. Or there could be a default tail function that returns default(TOut) but that might be a little too magical.

  3. Michael Valenty says:

    Finally is no longer required. Now it just requires a short-circuiting filter before reaching the end of the chain. If it goes too far it will throw and EndOfChainException. With that done, I can move Finally to an extension method that just adds a short-circuiting filter based on a delegate. That will simplify the Pipeline implementation since Finally is really just syntactic sugar.

  4. Joshua Ewer says:

    Curious: Your pipeline itself isn’t an implementation of IFilter. Have you thought about how/if/why you would compose multiple pipelines together?

  5. Michael Valenty says:

    Composing pipelines would essentially create workflows which is pretty cool. The reason I didn’t do it originally was because I thought it would be awkward for Pipeline to implement IFilter with the ‘executeNext’ parameter. After thinking about your comment though, it occurred to me that I could just implement IFilter explicitly and have the best of both worlds. I made the change and pushed it to the ‘develop’ branch on github. I’ll update the NuGet package shortly after I make sure it’s solid. Thanks for your comment!

  6. I had given up on AOP because Aspect# was dog slow the last time I investigated it. I might have to give it another go now. Thanks for sharing.

  7. Joshua Ewer says:

    Shoot, now I wish i would have done it as a pull request, just for the geek cred :-) I’ll checkout your develop branch, but I’m excited to pull this into a POC. I’ll let you know what comes of it, but it’s looking great so far. Excellent work!

  8. Just want to say your article is as astonishing.
    The clearness on your post is simply great and i could think you are a professional on this subject.
    Fine together with your permission allow me to take hold of your RSS
    feed to keep updated with coming near near post. Thanks a million and
    please carry on the gratifying work.

RSS feed for comments on this post, TrackBack URI

Leave a Comment