chain

Result Pattern

Previously I have always generated custom exceptions when I know that the state of the system will causes errors when running. Null checking method parameters at the start of a method being a simple example of this. I threw them up to be dealt with, or handled them in the catch locally.

However recently I have started using a Result pattern instead. This seems to produce code that is much easier to read, and a lot cleaner. It also reserves the use of exceptions for well… exceptions; things that have happened that were unexpected. I have found that this compliments async Tasks and API services nicely, letting you pass very specific errors back that were generated at source.

The two classes themselves are fairly straight forward. One handles void returns and the other handles typed returns 👇

public class Result
{
    public bool IsSuccess { get; }
    public string Error { get; private set; }
    public bool IsFailure => !IsSuccess;

    protected Result(bool success, string error)
    {
        if (success && error != string.Empty)
        {
            throw new InvalidOperationException();
        }

        if (!success && error == string.Empty)
        {
            throw new InvalidOperationException();
        }

        IsSuccess = success;
        Error = error;
    }

    public static Result Fail(string message)
    {
        return new Result(false, message);
    }

    public static Result<T> Fail<T>(string message)
    {
        return new Result<T>(default(T), false, message);
    }

    public static Result Ok()
    {
        return new Result(true, string.Empty);
    }

    public static Result<T> Ok<T>(T value)
    {
        return new Result<T>(value, true, string.Empty);
    }
}

public class Result<T> : Result
{
    private readonly T _value;
    public T Value
    {
        get
        {
            if (!IsSuccess)
                throw new InvalidOperationException();

            return _value;
        }
    }

    protected internal Result(T value, bool isSuccess, string error)
        : base(isSuccess, error)
    {
        _value = value;
    }
}

Given the above, I have drawn up a few examples below to demonstrate it’s use. First off, we have our book object and service. Then you have the API controller. I have used an API controller for the example, but the principle applies for anything. DLL’s, Console Apps etc 👍


public class Book
{
    public string Name { get; set; }
    public string Author { get; set; }
    public int StarRatingToFive { get; set; }
}

public class BookService
{
    public string InvalidStarRating = "The star rating is invalid. Please enter a value up to and including 5";
    public string TitleNotFound = "No matching title could be found";

    private IEnumerable<Book> books = new List<Book>
        {
            new Book
            {
                Name = "The Bone Ships",
                Author = "RJ Barker",
                StarRatingToFive = 4
            },
            new Book
            {
                Name = "Skyward",
                Author = "Brandson Sanderson",
                StarRatingToFive = 5
            }
        };

    public Result<Book> GetBookByName(string bookName)
    {
        bookName = bookName ?? string.Empty;
        var book = books.FirstOrDefault(x => x.Name.ToLower() == bookName.ToLower());

        if (book is null)
        {
            return Result.Fail<Book>(TitleNotFound);
        }

        return Result.Ok(book);
    }

    public Result<IEnumerable<Book>> GetBookByStarRating(int starRating)
    {
        if (starRating > 5 || starRating < 0)
        {
            return Result.Fail<IEnumerable<Book>>(InvalidStarRating);
        }

        var matchingBooks = books.Where(x => x.StarRatingToFive == starRating);
        return Result.Ok(matchingBooks);
    }
}

[ApiController]
[Route("[controller]")]
public class BookController : ControllerBase
{
    [HttpGet]
    public IActionResult Get(string name)
    {
        var bookService = new BookService();
        var getBookByName = bookService.GetBookByName(name);

        if (getBookByName.IsFailure && getBookByName.Error == bookService.TitleNotFound)
        {
            return NotFound(getBookByName.Error);
        }

        return Ok(getBookByName.Value);
    }

    [HttpGet]
    public IActionResult Get(int starRating)
    {
        var bookService = new BookService();
        var getBookByRating = bookService.GetBookByStarRating(starRating);

        if (getBookByRating.IsFailure && getBookByRating.Error == bookService.InvalidStarRating)
        {
            return BadRequest(getBookByRating.Error);
        }

        return Ok(getBookByRating.Value);
    }
}

These are very basic examples, but hopefully they are easy to extrapolate into more complicated scenarios. I find very much that they help enforce the single responsibility principle, and break things up into smaller tasks that can be chained together depending on the outcome of each result.

The result pattern chains very nicely as well. If you call a method that returns a Result from within another method that returns a Result it’s very simple to pass that on up the chain. One benefit of doing this is you can expand on the error message at each level, adding information that perhaps wasn’t available further down the chain ⛓

When dealing with library’s that do throw exceptions, I prefer to catch these at source and Result.Fail them with an appropriate message, then treat like every other Result.Fail

👋