2025-01-20

It is more explicit than throwing an Exception since the operation result type is specified explicitly as the method’s return type. That makes it more evident than knowing what type of exceptions the operation and its dependencies can throw.Another advantage is the execution speed; returning an object is faster than throwing an exception. Not that much faster, but faster nonetheless.Using operation results is more flexible than exceptions and gives us design flexibility; for example, we can manage different message types like warnings and information.

Disadvantages

Using operation results is more complex than throwing exceptions because we must manually propagate it up the call stack (i.e., the result object is returned by the callee and handled by the caller). This is especially true if the operation result must go up multiple levels, suggesting this pattern may not be the most suitable.It is easy to expose members not used by all scenarios, creating a bigger API surface than needed, where some parts are used only in some cases. But, between this and spending countless hours designing the perfect system, sometimes exposing an int? Value { get; } property is the best option. Nonetheless, always try to reduce that surface to a minimum and use your imagination and design skills to overcome those challenges!

Summary

In this chapter, we visited multiple forms of the Operation Result pattern, from an augmented Boolean to a complex data structure containing messages, values, and success indicators. We also explored static factories and private constructors to control external access. Furthermore, after all that exploration, let’s conclude that there are almost endless possibilities around the Operation Result pattern. Each specific use case should dictate how to make it happen. From here, I am confident you have enough information about the pattern to explore the many possibilities yourself, and I highly encourage you to.The Operation Result pattern is perfect for crafting strongly typed return values that self-manage multiple states (error and success) or support complex states (like partial success). It is also ideal for transporting messages that are not necessarily errors, like information messages. Even in its simplest form, we can leverage the Operation Result pattern as a base for extensibility since we can add members to the result class over time, which would be impossible for a primitive type (or any type we don’t control).

The HttpResponseMessage class returned by the methods of the HttpClient class is an excellent example of a concrete implementation of the Operation Result pattern. It contains a single message exposed through the ReasonPhrase property. It exposes a complex success state through the StatusCode property and a simple success indicator through its IsSuccessStatusCode property. It also contains more information about the request and response through other properties.

At this point, we would usually explore how the Operation Result pattern can help us follow the SOLID principles. However, it depends too much on the implementation, so here are a few key points instead:

  • The OperationResult class encapsulates the result, extracting that responsibility from the other system’s components (SRP).
  • We violated the ISP with the Value property in multiple examples. This infringement has a minor impact that we fixed as an example of overcoming this challenge.
  • We could compare an operation result to a DTO but returned by an operation (method) instead of a REST API endpoint. From there, we could add an abstraction or stick with returning a concrete class, but sometimes using concrete types makes the system easier to understand and maintain. Depending on the implementation, this may break different principles.

When the advantages surpass the minor impacts of those kinds of violations, it is acceptable to let them slide. Principles are ideals and are not applicable in every scenario—principles are not laws.

Most design decisions are trade-offs between two imperfect solutions, so you must choose which downsides you prefer to live with to gain the upsides.

This chapter concludes Section 3: Components Patterns and leads to Section 4: Application Patterns, where we explore higher-level design patterns.

Leave a Reply

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