2025-01-21

It is easy to rely on throwing exceptions when an operation fails. However, the Operation Result pattern is an alternative way of communicating success or failure between components when you don’t want to use exceptions. One such reason could be that the messages are not errors or that treating an erroneous result is part of the main flow, not part of a side catch flow.A method must return an object containing one or more elements presented in the Goal section to be used effectively. As a rule of thumb, a method returning an operation result should not throw exceptions. This way, consumers don’t have to handle anything other than the operation result itself.

You can throw exceptions for special cases, but at this point, it is a judgment call based on clear specifications or facing a real problem. For example, a critical event that happens, like the disk is full, would be a valid use case for an exception because it has nothing to do with the main flow, and the code must alert the rest of the program about the system failure.

Instead of walking you through all of the possible UML diagrams, let’s jump into the code and explore multiple smaller examples after taking a look at the basic sequence diagram that describes the simplest form of this pattern, applicable to all examples:

 Figure 13.1: Sequence diagram of the Operation Result design patternFigure 13.1: Sequence diagram of the Operation Result design pattern 

The preceding diagram shows that an operation returns a result (an object), and then the caller handles that result. The following examples cover what we can include in that result object.

Project – Implementing different Operation Result patterns

In this project, a consumer (REST API) routes the HTTP requests to the correct handler. We are visiting each of those handlers one by one to create an incremental learning flow from simple to more complex operation results. This project shows you many ways to implement the Operation Result pattern to help you understand it, make it your own, and implement it as required in your projects.Let’s start with the REST API.

The consumer

The consumer of all examples is the Program.cs file. The following code from Program.cs routes the HTTP requests toward a handler:

app.MapGet(“/simplest-form”, …);
app.MapGet(“/single-error”, …);
app.MapGet(“/single-error-with-value”, …);
app.MapGet(“/multiple-errors-with-value”, …);
app.MapGet(“/multiple-errors-with-value-and-severity”, …);
app.MapGet(“/static-factory-methods”, …);

Next, we cover each use case one by one.

The simplest form of the Operation Result pattern

The following diagram represents the simplest form of the Operation Result pattern:

 Figure 13.2: Class diagram of the Operation Result design patternFigure 13.2: Class diagram of the Operation Result design pattern 

We can translate that class diagram into the following blocks of code:

app.MapGet(
    “/simplest-form”,
    (OperationResult.SimplestForm.Executor executor) =>
    {
        var result = executor.Operation();
        if (result.Succeeded)
        {
            // Handle the success
            return “Operation succeeded”;
        }
        else
        {
            // Handle the failure
            return “Operation failed”;
        }
    }
);

The preceding code handles the /simplest-form HTTP requests. The highlighted code consumes the following operation:

namespace OperationResult.SimplestForm;
public class Executor
{
    public OperationResult Operation()
    {
        // Randomize the success indicator
        // This should be real logic
        var randomNumber = Random.Shared.Next(100);
        var success = randomNumber % 2 == 0;
        // Return the operation result
        return new OperationResult(success);
    }
}
public record class OperationResult(bool Succeeded);

The Executor class contains the operation to execute represented by the Operation method. That method returns an instance of the OperationResult class. The implementation is based on a random number. Sometimes it succeeds, and sometimes it fails. You would usually code real application logic in that method instead. Moreover, in an actual application, the method should have a proper name representing the operation, like PayRegistrationFees or CreateConcert.The OperationResult record class represents the result of the operation. In this case, a simple read-only Boolean value is stored in the Succeeded property.

I chose a record class because there is no reason for the result to change. To know more about record classes, have a look at Appendix A.

In this form, the difference between the Operation method returning a bool and an instance of OperationResult is small, but it exists nonetheless. By returning an OperationResult object, you can extend the return value over time, adding properties and methods to it, which you cannot do with a bool without updating all consumers.Next, we add an error message to the result.

Leave a Reply

Your email address will not be published. Required fields are marked *