Wednesday, 20 February 2013

Implementing the Chain of Responsibility Design Pattern

Earlier this week I made use of the Chain of Responsibility design pattern to solve a fairly straight-forward problem. I've always been careful when trying to apply design patterns, it's easy to get carried away and try to force a design pattern to a problem that doesn't really require it. In this case, I felt it was justified and hopefully you'll think the same!

The Context

I'll give some context to the problem before we go into exactly what the Chain of Responsibility pattern is. I was working on a HTTP service (built on ASP.NET Web API) which had an API controller action that accepted requests for some data stored in a persistent repository. My requirement was to inspect the incoming request object, and depending on certain characteristics of the request, log the request object data to zero or many different output locations. This is to enable reporting on the types of requests that the service receives. The output locations were disparate data stores such as an XML file, Excel file and SQL Server Database. The key point was that to log the request I had to inspect the content of the request, and depending on some criteria, log it to none or certain data stores. Furthermore, certain requests had to be ignored and therefore not logged.

Chain of Responsibility

When I read this requirement I knew immediately there was a design pattern for this type of problem. I had taken an Object-Oriented design pattern course at university around six years ago but couldn't quite remember the name of the pattern. After having a quick look on the GoF website, I eventually found it - Chain of Responsibility (CoR). CoR is a software design pattern that has been classified into the "behavioral" category of patterns - these are patterns that broadly deal with objects interacting with other objects. The idea is that you have multiple handler objects chained together just like a linked list data structure. An incoming request object is passed over to the first handler in the chain, the handler (depending on its "responsibility"), inspects the request and can either:
  1. Handle the request and not pass the request down the chain of handlers
  2. Handle the request but still pass the request down the chain of handlers
  3. Not handle the request and pass the request down the chain to the next handler
Note that before a handler sends the request object down the chain, it is also responsible for checking if it is actually linked to another handler. If a handler isn't linked to another handler, it is the last handler in the chain and therefore consumes the request.

Applying CoR to the Problem

In my case, the incoming data request to the API was my CoR request that required "handling" from a logging perspective. I implemented a separate concrete logging handler class for each of the concrete data stores (XML, Excel and SQL Server). Each logging handler inspected the request and logged the request if it matched the criteria for the handler before passing the request to the next handler. Therefore, in my case, the CoR handler behaviour corresponded to point two above.

The Implementation

Note that I've stripped down the code below (especially for each concrete handler) so that it is easier to see what's going on from a design pattern perspective.

The first step was to write an abstract base handler that defined the behaviour that each of my handlers will support. The HandlerBase class below is generic enough to be reused in any case where you require a CoR implemented. The single type parameter "TRequest" is the type of the object that will go through your chain (can be any .NET type).
public abstract class HandlerBase<TRequest>
{
    public HandlerBase<TRequest> Successor
    { 
        protected get; 
        set; 
    }

    public abstract void HandleRequest(TRequest request);
}
The HandlerBase class has one property, which allows users of a handler to set a successor to this handler (the next handler in the chain) and it has an abstract method that all derived classes will need to implement - HandleRequest. You will see further below how we use the Successor property when setting up our chain, and how each concrete handler (a class that derives from HandlerBase) uses the HandleRequest method on its successor to pass the request down the chain.

Next, I implemented each of my concrete handlers - that is, a handler for logging to an XML document, Excel document and SQL server.
public class LogRequestToXmlHandler : HandlerBase<ApiDataRequest>
{
    public override void HandleRequest(ApiDataRequest request)
    {
        if (request.Foo == Something && request.Bar == SomethingElse)
            // Log request to xml document

        // Pass on the request to the next handler, if any
        if (base.Successor != null)
            base.Successor.HandleRequest(request);
    }
}
Notice how the concrete handler LogRequestToXmlHandler derives from HandlerBase and passes the type of the object that is going through the chain - ApiDataRequest. Also notice that because we're inheriting from an abstract base class containing one abstract method - we're forced by the compiler to override the HandleRequest method. This method accepts one parameter, the request object. It is in this method that you will place any specific handling logic for the request object - I've added some pseudo-code to demonstrate this. The last couple of lines inspect the Successor property (inherited from the base class), if it isn't null - then we call the HandleRequest on it, thus passing our request down the chain. If Successor returns null, then the current handler is the last in the chain and we do nothing - effectively consuming the request. For completeness, the other handlers are below.
public class LogRequestToExcelHandler : HandlerBase<ApiDataRequest>
{
    public override void HandleRequest(ApiDataRequest request)
    {
        if (request.Foo == Something && request.Bar == SomethingElse)
            // Log request to excel document

        if (base.Successor != null)
            base.Successor.HandleRequest(request);
    }
}

public class LogRequestToDbHandler : HandlerBase<ApiDataRequest>
{
    public override void HandleRequest(ApiDataRequest request)
    {
        if (request.Foo == Something && request.Bar == SomethingElse)
            // Log request to database

        if (base.Successor != null)
            base.Successor.HandleRequest(request);
    }
}
Now that we have our handlers, the final step is to setup our chain so that it's ready to handle requests. The code below shows how the handlers are chained together.
HandlerBase<ApiDataRequest> logToXmlHandler
    = new LogRequestToXmlHandler();

HandlerBase<ApiDataRequest> logToExcelHandler
    = new LogRequestToExcelHandler();

HandlerBase<ApiDataRequest> logToDbHandler
    = new LogRequestToDbHandler();

logToXmlHandler.Successor = logToExcelHandler;
logToExcelHandler.Successor = logToDbHandler;
The first step above was to initialise each handler that will be going into the chain. We then use the Successor property to chain the three handlers together. The beauty of using this pattern is that you can chain however many handlers you want and even make the chain configurable so that it is setup at runtime depending on some settings in a config file.

Now, when a new request came through my API, it was a simple case of invoking the HandleRequest method on the first handler in the chain and passing the ApiDataRequest object through as a parameter. This sent the object down the chain, allowing each log handler to inspect it and decide whether to log it to their individual outputs.
logToXmlHandler.HandleRequest(request);
* Update *

One thing that I missed in this post and is worth mentioning is a scenario where you don't want each concrete handler to decide whether to pass the request on down the chain. In this case, the request would go to every handler in the chain regardless. One way to accomplish this is to update the HandlerBase class as follows:
public abstract class HandlerBase<TRequest>
{
    public HandlerBase<TRequest> Successor
    { 
        protected get; 
        set; 
    }

    public void HandleRequest(TRequest request)
    {
        Handle(request);

        if (Successor != null)
            Successor.HandleRequest(request);
    }

    protected abstract void Handle(TRequest request);
}
The updated HandlerBase now implements a public HandleRequest method which first delegates the handling logic to an abstract method (Handle) which will be overridden by a concrete handler. Once a request has been handled, a test is made to check if there is a successor and if there is, then the request is passed on. What this now means is that each concrete handler will implement just one method, Handle, and not need to worry about whether to pass the request on or not - that is done automatically in the HandleRequest method. An example concrete handler is below:
public class LogRequestToXmlHandler : HandlerBase<ApiDataRequest>
{
    protected override void Handle(ApiDataRequest request)
    {
        // Check request and log it, if required
    }
}

No comments:

Post a Comment