Exception Handling

Resilience refers to a system’s ability to recover gracefully from failures, handle unexpected situations, and continue delivering what it’s supposed to do. To achieve this, engineers use key resilience patterns like circuit breakers, retries, fallbacks, and throttling.

Adding resilience into a codebase usually requires writing boilerplate code, which is often tedious, unproductive, and error-prone.

Let’s see how Metalama helps.

Resilience with Polly

Polly is the go-to library for implementing resilience in .NET. It implements the most-used policies, is actively maintained, and there is probably no need to reinvent the wheel.

However, adding Polly to your code can still require some boilerplate code: you still need to pull dependencies and wrap all methods into delegate calls—for each method.

With Metalama, you can add Polly without any boilerplate.

Benefits

Compared to adding Polly manually, adding it with Metalama has the following benefits:

  • Clean code: There’s no need to wrap your code in a delegate call, so it remains crystal clear and is easier to maintain.
  • Easily modify the pattern: You can easily replace Polly with another library or remove it altogether if you want.

Example

You can create an [AddPolicy] Metalama aspect that pulls the Polly dependency and wraps your method into a delegate code:

public class CalculatorService
{
    [AddPolly( "MyPolicy" )]
    public int Add( int a, int b )
    {
        // Your code here
        return a + b;
    }
}

See this commented example to learn how to create such aspects.

Show me my transformed code!

Resources

Resilience without Polly

You can, of course, create exception-handling aspects without Polly. See these examples to get some inspiration.

Enriching exception stacks

In diagnosing software problems, context is everything. Exception call stacks are a good example of this context, but they are not enough: they lack parameter values that give you an idea of the application state.

You can use an aspect to append the parameter values to any Exception flying through your method. When reporting the exception, you can add the parameter values to the call stack.

Example

Suppose we have the following exception:

System.NullReferenceException: Object reference not set to an instance of an object
   at CustomerService.GetCustomerCreationDate(Int32)
   at HomeController.Index()
   ...

To continue your investigation, you would need to know for which value of customerId the GetCustomer method returns null.

You create an EnrichExceptionAttribute aspect that automatically wraps the whole method body in a try-catch block and enriches the exception with the parameter values. You can see the full code of that aspect here.

However, solely defining an aspect is not enough. You need to apply it to all your methods, so you add this class to your project:

internal class Fabric : ProjectFabric
{
    public override void AmendProject(IProjectAmender amender) =>
        amender
            .SelectTypes()
            .SelectMany(type => type.Methods)
            .AddAspectIfEligible<EnrichExceptionAttribute>();
}

Now, instead of just reporting exception.ToString(), you can append the result of exception.GetContextInfo() to get the following exception stack:

System.NullReferenceException: Object reference not set to an instance of an object
   at CustomerService.GetCustomerCreationDate(Int32)
   at HomeController.Index()
   ...
with
  CustomerService.GetCustomerCreationDate(123)

Now you see (at the bottom of the block) for which value of customerId your code fails.

Show me how it works!

Metalama benefits

  • You can just do it! You would probably not be willing to implement this trick if it were not free. With Metalama, you can just do it.
  • Clean code. The exception handling code is generated on-the-fly during compilation, so your code remains crystal clear and is easier to maintain.
  • Easily add or remove. You can add or remove the feature at any time. You can also enable logging only in select build configurations.

Resources