Outcome.Net Basics, Part I

About a year ago, there was a whole week where I did nothing but write dozens of tiny, repetitive result wrapper classes. Or, at least, that was the plan. Instead, I wrote Outcome. 

So what do I mean when I talk about result wrappers? Take a simple method like this:

public function int GetTotal() {
    //<Do some work and produce an int called result>
    
    return result;
}

Standard stuff. But what if there's a chance something could go wrong and you need to do some flow control and possibly show a friendly message to the user? 

If your mind goes straight to exceptions, you aren't alone. Exceptions have their place, but using exceptions for flow control is an anti-pattern, so many of us tend to create thin classes that wrap the return value along with a Success bit and a list of messages. That's a result wrapper.

A real-world result wrapper for our GetTotal() method might look like this:

public class TotalResult
{
    public List<string> Messages = new List<string>();
    public bool Success = false;
    public int Value = 0; 
}

And then our GetTotal() method would become:

public TotalResult GetTotal()
{
    //<Do some work and return an int called result, but there's
    //a chance something could go wrong, in which case we want to
    //show the user a failure message.>

    if (workSucceeded)
    {
        return new TotalResult() { Success = true, Value = result};
    }

    var failResult = new TotalResult() { Success = false };
    failResult.Messages.Add("Something went wrong, and the user needs to know!");
      
    return failResult;
}

If we were calling GetTotal() in the real world, we would get back a TotalResult, check the Success bit, and if it was true move on. If Success was false, perhaps we would try another tactic or display our messages to the user. 

This pattern works fine. There are a few drawbacks, the biggest being that you have to hand craft those result wrappers, and some projects contain hundreds of them. That's a lot of plumbing code!

It also introduces clutter. These wrappers are not very expressive, and they introduce new concerns in the form of success handling and messages handling. To me, it feels like a forgivable violation of the Single Responsibility Principle - not a disaster, but a step away from clean code.

Enter Outcome.NET

On the surface, Outcome.NET's main goal is to eliminate the need for creating these wrappers at all and to make the experience of dealing with wrappers as expressive and easy as possible. And if we do a good enough job at that, we might be able to further minimize that SRP violation.

If we used Outcome in our GetTotal() method, it would look like this:

public IOutcome<int> GetTotal()
{
    //<Do some work and return an int called result, but there's
    //a chance something could go wrong, in which case we want to
    //show the user a failure message.>

    if (workSucceeded)
    {
        return Outcomes.Success<int>()
                       .WithValue(result);
    }

    return Outcomes.Failure<int>()
                   .WithMessage("Something went wrong, and the user needs to know!");

}

Those calls to Outcomes.Success and Outcomes.Failure produce an IOutcome, which is shaped a lot like TotalReponse, but generic and armed with fluent helpers designed to make the intention of your code is as clear as possible.

Good things happen to your maintainability story when you work with consistent interfaces and eliminate plumbing classes, but I've barely touched the greater benefits of using Outcome. A few points that I plan to cover in future posts include:

  • AJAX. Outcome shines when building out AJAX calls in MVC applications. Outcome objects serialize nicely to JSON, so JavaScript tools are emerging that generically manage "please wait" spinners, and then handle failures and messages from the server. It's been a big time saver.
  • Notification Pattern. When working with complex logic that involves many layers of classes, Outcome becomes a great implementation of the Notification Pattern, because you can roll up messages from several Outcomes and add value to them as you go.
  • Cleaner Code. The example code above is a bit more expressive than the hand-written wrapper, but there are also benefits on the calling side, especially when composing complex logic.

Want to take Outcome for a spin? Check it out on GitHub!

So, what do you think of Outcome.NET? Let me know in the comments! 



Brian MacKay, senior App vNext consultant and founder of Kinetiq, is a software developer, entrepreneur, chess nerd, kick boxer, musician, writer, and father. He writes about the business of software and hard-core technical problems.