Chain of responsibility

Summary

The chain of responsibility pattern provides us with a way to decouple the sender of a request to a receiver by giving multiple objects a chance to handle the request. In the request chain each class can perform its own logic, exit the chain, or call the next class.

Real talk

A simple real world example comes from my startup Perkbound, an employee recognition and rewards platform. In Perkbound, managers and employees can give other employees recognitions which always give the recipient points. The difference between a manager and employee though is that when a manager has zero points to give their account is charged.

Both managers and employees send recognition requests to the same endpoint, the only difference is the request object type. following the single responsibility principle and the “Tell, don’t ask” principle I created two handler classes that perform logic based on the type of object provided.

The request

The request classes in this case are simple types that both inherit from the same base class. The handlers in the CoR pattern will hand off the requests based on the type of request sent.

public class RecognizeRequest
{
    public int CompanyId { get; set; }
    public int FromId { get; set; }
    public int ToId { get; set; }
    public string Message { get; set; }
    public decimal Amount { get; set; }
}

public class ManagerRecognizeRequest : RecognizeRequest { }

public class EmployeeRecognizeRequest : RecognizeRequest { }

Stepping into the caller

Typically I'll go through a service which calls a provider/store to do the real logic. Below we jump straight to the provider where the IRecognitionHandler is injected.

public class RecognitionProvider : IRecognitionProvider
{
    private readonly IRecognitionHandler _recognitionHandler;
    
    public RecognitionProvider(IRecognitionHandler recognitionHandler)
    {
        _recognitionHandler = recognitionHandler;
    }
    
    public void Recognize(RecognizeRequest request)
    {
        _recognitionHandler.Recognize(request);
    }
}

When the service calls _recognitionProvider.Reconize(request); the first link in the chain is called and we start our descent.

The first link

When entering the first link in the chain the Recognize method will first check if the type of request passed in is the one we are looking for. If it is not the correct type the next handler is called which happens to be EmployeeRecognition.

public class ManagerRecognition : IRecognitionHandler
{
    private readonly IRecognitionHandler _nextHandler;
    
    public ManagerRecognition(IRecognitionHandler nextHandler)
    {
        _nextHandler = nextHandler;
    }
    
    public void Recognize(RecognizeRequest request)
    {
        if (request.GetType() != typeof(ManageRecognizeRequest))
        {
            _nextHandler.Recognize(request);
            return;
        }
        
        //do custom logic to charge managers account
    }
}

End of the line

This simple example of CoR only has two links. This last element simply throws an exception if no handler has been found that can properly handle the request.

public class EmployeeRecognition : IRecognitionHandler
{
    public void Recognize(RecognizeRequest request)
    {
        if (request.GetType() != typeof(EmployeeRecognizeRequest))
        {
            throw new HandlerException("No handler available for the given request");
        }
        
        //do custom logic to remove points from user
    }
}