Messaging with CPS

The great thing about feedback is that it’s an opportunity. An opportunity to learn, improve and sometimes even reject what you previously held to be true. All the questions that arose from my earlier posts got me thinking and discussions with other developers lead me to some new insights as well as more questions in my own mind. I decided to revisit the LogIn example that I used in the original post to see how adopting some of those ideas would work out.

The main change is a big one and centres around the complete removal of the pipe and filter “framework” (I know some people don’t particularly care for it) replaced by the idea of CPS otherwise known as Continuation Passing Style which basically turns normal sequential steps inside-out from the point of view of the caller. In the context of our pipe and filters approach this means we no longer have separate classes for each filter but rather these become just plain old methods within the class that represents the task or use case at hand. Our use case class is completely self contained in terms of the steps to be taken by the message rather than composed from the outside as was the case with the Register methods previously.

What is a Continuation?

Before diving in let’s define what a continuation actually is. Wikipedia has this to say on continuations:

First-class continuations are a language’s ability to completely control the execution order of instructions. They can be used to jump to a function that produced the call to the current function, or to a function that has previously exited. One can think of a first-class continuation as saving the state of the program. However, it is important to note that true first-class continuations do not save program data, only the execution context. This is illustrated by the “continuation sandwich” description:

Say you’re in the kitchen in front of the refrigerator, thinking about a sandwich. You take a continuation right there and stick it in your pocket. Then you get some turkey and bread out of the refrigerator and make yourself a sandwich, which is now sitting on the counter. You invoke the continuation in your pocket, and you find yourself standing in front of the refrigerator again, thinking about a sandwich. But fortunately, there’s a sandwich on the counter, and all the materials used to make it are gone. So you eat it. :-)

and this to say on Continuation Passing Style

There is another excellent explanation on continuations and continuation passing style (CPS) which can be found here. It goes into a lot of detail about what CPS can give you as a developer but essentially, for our purposes, as C# does not support first-class continuations, what we’re talking about here is really an emulation of sorts through callbacks that give us the continuation passing style and let us invoke the rest of the computation from within the currently executing step. Hopefully, the LogIn example below illustrate this more clearly:

The LogIn Example

First off, we’ll define a class with the dependencies we need to fulfil the use case:


public class LogInTask
{
    public LogInTask(IClientDetailsDao clientDao, IUserDetailsDao userDao, IMembershipService membershipService, IBus bus)
    {
        _clientDao = clientDao;
        _userDao = userDao;
        _membershipService = membershipService;
        _bus = bus;
    }

    private readonly IClientDetailsDao _clientDao;
    private readonly IUserDetailsDao _userDao;
    private readonly IMembershipService _membershipService;
    private readonly IBus _bus;
}

Next we’ll define a method to do what was originally done by a separate filter class in the pipe and filters approach:


public class LogInTask
{
    ...
	
    private void CheckUserSuppliedCredentials(LogInMessage input, Action onNext)
    {
        if(!string.IsNullOrEmpty(input.Username) && !string.IsNullOrEmpty(input.Password))           
            onNext();
        else
            input.Status = "Invalid credentials";
    }
}

Notice that having checked the supplied credentials we either callback via the onNext invocation or we set a reason for the failure. We’re not throwing an exception or even implementing an interface like IStopProcessing defined in Part 2. There’s no need to set a boolean Stop property here.

We define the rest of the methods in a similar manner:


public class LogInTask
{
    ...

    private void CheckApiKeyIsEnabledForClient(LogInMessage input, Action onNext)
    {
        input.ClientDetails = _clientDao.GetDetailsFrom(input.ApiKey);
        if(input.ClientDetails != null)
            onNext();
        else
            input.Status = "ApiKey not valid"
    }
	
    private void IsUserLoginAllowed(LogInMessage input, Action onNext)
    {	
        if(input.ClientDetails.IsEnabled)
            onNext();
        else
            input.Status = "Client not enabled";
    }
	
    private void ValidateAgainstMembershipApi(LogInMessage input, Action onNext)
    {
        if(_membershipService.Validate(input.Username, input.Password))
            onNext();
        else
            input.Status = "User not found";
    }
	
    private void GetUserDetails(LogInMessage input)
    {
        input.UserDetails = _userDao.GetDetailsFrom(input.Username);
    }
	
    private void PublishUserLoggedInEvent(LogInMessage input)
    {
        _bus.Publish(new UserLoggedInEvent{ Name = input.Username });
    }
}

The only thing left to do is to define the method that actually pushes the message through each step making use of the continuation style. As you saw, some of the methods take the message and an Action delegate to invoke, if and only if the message has successfully passed through the particular step in question. Our invoke method looks like this:


public void Invoke(LogInMessage msg)
{
    CheckUserSuppliedCredentials(msg, () => {
        CheckApiKeyIsEnabledForClient(msg, () => {
            IsUserLoginAllowed(msg, () => {
                ValidateAgainstMembershipApi(msg, () => {
                    GetUserDetails(msg);
                    PublishUserLoggedInEvent(msg);
                });
            });
        });
    });
}
	

Hopefully the indentation helps to clarify that only if the first call succeeds does the second call get invoked. For example, the CheckUserSuppliedCredentials method does not invoke the onNext callback if the supplied values are invalid. Instead it sets the Status property and the rest of the chain is never called. Similarly, if the credentials are valid then CheckApiKeyIsEnabledForClient is invoked but it too will only allow the rest of the chain to proceed if the call to retrieve the actual details from the database does not result in a null value. Each check in the chain either succeeds and therefore invokes the next step or sets a reason as to why it can’t continue. Once the final check, ValidateAgainstMembershipApi, has succeeded both the GetUserDetails and PublishUserLoggedInEvent mehods are invoked as part of a single continuation.

We can execute this use case like so:


var task = new LogInTask(new ClientDetailsDao(), 
                         new UserDetailsDao(), 
                         new MembershipService(), 
                         new MySimpleBus()
);

var msg = new LogInMessage { 
    Username = "", 
    Password = "1234", 
    ApiKey = Guid.Parse("a9aafe3f-76d3-4304-a39e-cf05edb870f2") 
};

task.Invoke(msg);

We can now interrogate the Status property of the message immediately after the call to Invoke. If all is well it should be empty. If, on the other hand, as the example message shows, the Username property was not supplied, we would get back the reason for the failure:


Console.WriteLine(msg.Status); // Invalid credentials

Just like the original pipe and filters approach, once the message has successfully passed through the steps it will contain data we can return to the client.

On Error Goto

One way we could extend the example further would be by passing in a continuation callback that should be called in the event of an error. This means that instead of setting the Status property of the message and then evaluating it after the call to Invoke, we instead supply a callback that is executed when the error occurs. This does add complexity to the Invoke method though as it would then look like this:


public void Invoke(LogInMessage msg)
{
    CheckUserSuppliedCredentials(msg, () => {
        CheckApiKeyIsEnabledForClient(msg, () => {
            IsUserLoginAllowed(msg, () => {
                ValidateAgainstMembershipApi(msg, () => {
                    GetUserDetails(msg);
                    PublishUserLoggedInEvent(msg);
                }, error => Log(error));
            }, error => Log(error));
        }, error => Log(error));
    }, error => Log(error));
}
	

But now it’s not only starting to get complex but also real ugly with repetitive calls to the same error handling logic. Of course, if you want to do different things depending on which step failed then you may be able to justify it but my preferred approach would be to keep error handling outside of the chain by taking advantage of aspect oriented programming techniques as I described in part 2 which are all still completely applicable with this style. Ultimately your choice will be dictated by the context of the use case you’re attempting to implement and how complex it is.

So now that we have an alternative to the original approach let’s compare them.

Advantages

  • In the continuation style we have a single class where the work is defined. This means you can see the entire implementation in one place without having to go and look for the filter class that implements one of the steps. The context of the use case is localised.
  • There are fewer classes to be coded
  • The flow of execution can be controlled more easily without throwing exceptions or requiring interfaces
  • Disadvantages

  • In the continuation style we have a single class where the work is defined. Er..that looks suspiciously like the first advantage listed above. Yes, that’s right, it can also be a disadvantage because now, unlike the pipe and filters approach, we’ve lost the ability to dynamically compose our pipeline at runtime.
  • More code in one place means higher complexity and potentially more risk of bugs creeping in – In the pipeline approach every filter class does one simple thing making it highly cohesive and easy to replace without touching other code
  • A complex use case can make for a hard to read Invoke method
  • I’m sure there are other advantages and disadvantages to both approaches but those are just a few that immediately come to mind. One thing it does give you though is options and if you so choose you could mix and match. Personally I would stick with one style or the other because as I tried to stress in the original article I want a consistent and uniform code base as much as possible, mainly because different ways of doing the same thing ultimately lead to a hard to maintain system.

    Summary

    Continuation passing is a technique we can use with messaging that provides another way of achieving the same goal as the pipe and filters approach of pushing a message through a series of steps to satisfy a given use case. Which one you choose is largely a matter of preference but like any technique it has to be traded off against the alternatives. Whilst on the surface not having to implement a pipeline “framework” can seem appealing keep in mind that you lose the ability to compose the pipeline on the fly based on runtime conditions which can, in my opinion, be too big a price to pay. Personally, whilst I was open to the idea which prompted me to investigate it and see how it worked, (and yes, it does work) I think I’m more likely to stick with the pipeline approach as I just think it has more to offer (flexibility, readability, maintainability to name three). On the other hand, if you don’t need that degree of flexibility then CPS can make another useful addition to your tool belt. I know which one I prefer though.

    Advertisements
    Messaging with CPS

    5 thoughts on “Messaging with CPS

    1. RalfW says:

      Great were open to try out another approach!

      It´s a good example of the Principle of Mutual Oblivion (http://geekswithblogs.net/theArchitectsNapkin/archive/2013/08/19/messaging-for-more-decoupling.aspx): no processing step knows about a predecessor or successor. They all stay highly composable and can be tested easily.

      Calling onNext is not requesting a particular action to happen next. It´s just a signal the current processing step has finished. So whatever is next can be started.

      Although I share your assessment of this approach to be pretty verbose and maybe not so easy to read (if you design each processing step to have several outputs), I think it´s a valuable tool in the messaging tool belt. Sometimes events are preferrable, sometimes continuations, sometimes plain old functions (POF ;-) are suffcient. Or – if you like – your infrastructure based approach using a generic pipeline.

      In one regard I beg to differ, though. CPS is not (!) limiting the implementation to one class. So there is no danger of overloading classes with responsibilities. You just need to allow the integration (your Invoke() method) to be separated from the operations (e.g. CheckUserSuppliedCredentials()).

      Integration and operation are two distinct responsibilities, which can (and maybe should) be implemented on different classes.

      You´re already separating concerns like data access and login domain. So why stop there? Validation and authentication could be separated as well. Your processing steps are already independent of each other. That makes it easy to move them around onto different classes, if you like. Maybe authentication needs some resources validation doesn´t. Why not express that by defining two classes and injecting only the really needed resources? That would be to follow something like a “Dependency Segregation Principle” (as a complement to the Interface Segregation Principle) :-)

      1. The thing is Ralf, once you start separating out into separate classes you might as well have just gone with the pipe and filters approach in the first place. To me, the pipeline approach just feels better and, one other benefit we’ve not mentioned before, it’s a design pattern name that many people are instantly familiar with which aids communication and understanding, and is easier, in my opinion, to grasp than talking about continuations. We can go on separating all day or providing different ways of achieving the same end but but before you know it we’re surely back to a complex system if we go down that route. I’m all for learning different ways but I’ve still to be convinced of the merits of this approach. It’s certainly worthy of investigation if only to round out one’s knowledge but so far I still prefer pipe and filters.

        Over to you :)

        1. RalfW says:

          I´m not against you pipes and filters. Please understand me right.

          I just want to point out the orthogonality of stuff. Doing CPS or pipes and filters does not (!) depend on putting all in one class or everything in different classes.

          I´m all for simplicity. But what´s simple might be one thing for you (e.g. a class for each filter) and another for me (e.g. a function for every processing step). As I said: messaging does not depend on pipes and filters or CPS. It can be done with just plain old functions as well. And their simplicity is hard to beat, I guess ;-)

          More choice might be daunting at first. But still these choices all are reigned in by principles (e.g. IOSP, PoMO). So you can start at whatever end of a scale and still be doing messaging.

          However, once you seem to run into a limitation of the chosen implementation approach you don´t need to doubt the advantages of messaging. Rather just switch the implementation style and on you go with messaging.

          Example: state. Your original pipes and filters example works great as long filters don´t keep state. That´s ok in many cases – but not in all. Sometimes you want stateful processing steps. What then? As long as you promote just one approach (“instanciate the filter for each message”) this is hard to realize. But if you allow singleton filters, well, then this is problem anymore.

          Likewise your approach scatters logic across many classes. This makes classes less valuable as code aggregators, I think. There is no code construct to aggregate related functionality (except “soft” namespaces). Also it increases the overhead for each filter.

          That´s perfectly fine as one approach to implementation – but why limit yourself?

          Anyway, we agree more than we differ. I think this is a fruitful discussion.

          Over :-)

        2. No, I understand you perfectly, Ralf :)
          And I understand where you’re coming from with regard to different approaches too.
          Of course, it’s worth exploring all possible avenues, otherwise the nail-hammer principle applies.

          As for state, I don’t think it’s a hard problem to solve at all in the Pipe and Filters approach. It’s easy enough to supply in the Register method an instance created elsewhere, as opposed to “newing” one up every time but I understand the point you’re trying to make.

          Scattered logic? or reuse and composability? Everything’s a trade-off. There is no one right way.

          But yeah, we agree more than we differ :)

    Comments are closed.