In this case, we could have used a protected constructor instead or implemented a fancier way of instancing success and failure instances. Nonetheless, I decided to use this opportunity to show you how to lock a class in place without the sealed modifier, making extending by inheritance from the outside impossible. We could have built mechanisms in our classes to allow controlled extensibility (like the Template Method pattern), but for this one, let’s keep it locked in tight!
From here, the only missing pieces are the operation itself and the consumer of the operation. Let’s look at the operation first:
namespace OperationResult.StaticFactoryMethod;
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
if (success)
{
return OperationResult.Success(randomNumber);
}
else
{
var error = new OperationResultMessage(
$”Something went wrong with the number ‘{randomNumber}’.”,
OperationResultSeverity.Error
);
return OperationResult.Failure(error);
}
}
}
The two highlighted lines in the preceding code block show the elegance of this new improvement. I find this code very easy to read, which was the objective. We now have two methods that clearly define our intentions when using them: Success or Failure.The consumer uses the same code that we saw before in other examples, so I’ll omit it here. However, the output is different for a successful or a failed operation. Here is a successful output:
{
“succeeded”: true,
“value”: 80
}
Here is a failed output:
{
“succeeded”: false,
“messages”: [
{
“message”: “Something went wrong with the number ’37’.”,
“severity”: “Error”
}
]
}
As the two preceding JSON outputs show, each object’s properties are different. The only shared property of the two is the Succeeded property. Beware that this type of class hierarchy is harder to consume directly since the interface (the OperationResult class) has a minimal API surface, which is good in theory, and each sub-class adds different properties, which are hidden from the consumers. For example, it would be hard to use the Value property of a successful operation directly in the endpoint handler code. Therefore, when hiding properties, as we did here, ensure those additional properties are optional. For example, we can use this technique when sending the result to another system over HTTP (like this project does) or publish the operation result as an event (see Chapter 19, Introduction to Microservices Architecture, where we introduce event-driven architecture). Nevertheless, learning to manipulate classes using polymorphism will be helpful the day you need it.Next, let’s peek at some advantages and disadvantages of the Operation Result pattern.
Advantages and disadvantages
Here are a few advantages and disadvantages of the Operation Result design pattern.