Friday, June 11, 2010

Centralized Error Handling With Lambda

For many reasons it is often useful - and important - to catch exceptions in UI or other boundary classes like web-services. Out of many other the most important reasons are usually security, to avoid to show the information of an internal system error to the client, and traceability, to log the error for investigations.

The problem is, exception handling is quiet complicated code and is often highly redundant, what makes it awkward to keep it in sync.

The Common Approach For Exception Handling

As a simple sample, guess we are developing a web-service method that returns a Country object from data storage by a specified ISO 3-alpha.
[OperationContract]
public Country GetByIsoAlpha3_Simple(string alpha3)
{
    Logger.Trace("Entered GetByIsoAlpha3");
    try
    {
        ThrowHelper.CheckNullArg(alpha3);
        CountrySource source = new CountrySource();
        return source.GetByIsoAlpha3(alpha3);
    }
    catch (ArgumentException ex)
    {
        throw new FaultException(ex.Message);
    }
    catch (DbException ex)
    {
        Logger.LogEx(ex);
        throw new FaultException("There was error with the data storage.");
    }
    catch (Exception ex)
    {
        Logger.LogEx(ex);
        throw new FaultException("A internal error occurred");
    }
    finally
    {
        Logger.Trace("Leaving GetByIsoAlpha3");
    }
}

As we see, the real functionality is only two lines of code, the rest of the 25 lines are standard exception handling and the validation of the input values.

The problem with this method is, it's very dangerous remember all required tasks for standard exception handling. Does it handle all standard exceptions we usually handle? Might it be useful to log the ArgumentException? Do we really want to tell the caller that we had an error with the data storage? How can we ensure that all web methods do the same standard exception handling.

Extract The Exception Handling

A good way to ensure all standard exception handling is to move it out into a static helper class.
static class WsHelper
{
    public static T ExecuteWebMethod<T>(string methodName, Func<T> method)
    {
        Logger.Trace("Entered " + methodName);
        try
        {
            return method();
        }
        catch (ArgumentException ex)
        {
            throw new FaultException(ex.Message);
        }
        catch (DbException ex)
        {
            Logger.LogEx(ex);
            throw new FaultException("There was error with the data storage.");
        }
        catch (Exception ex)
        {
            Logger.LogEx(ex);
            throw new FaultException("A internal error occurred");
        }
        finally
        {
            Logger.Trace("Leaving " + methodName);
        }
    }
}

Using Lambda Expressions To Utilize The Helper

Now, we can utilize the ExecuteWebMethod method with a simple Lambda expression.
[OperationContract]
public Country GetByIsoAlpha3_Lambda(string alpha3)
{
    return WsHelper.ExecuteWebMethod("GetByIsoAlpha3_Lambda", () =>
    {
        ThrowHelper.CheckNullArg(alpha3);
        CountrySource source = new CountrySource();
        return source.GetByIsoAlpha3(alpha3);
    });
}
As we see, the code of our web method is reduced to six lines. Still three lines for our method specific work, two lines with braces and one line calling the helper method which gets a Func<T> that represents the implementation of the web method.

Possible Extensions

The exceptions handled in this sample are only exemplary. If you have some other common standard exceptions like a ValidationException or a general BusinessException, feel free to extend the ExecuteWebMethod.
For sure, you noticed the methodName parameter, provided for the ExecuteWebMethod operation. To get rid of this parameter we can use the StackTrace class to determine the calling method. In case of web services or other boundaries, this is a good approach. Nevertheless, be careful with StackTrace in highly utilized methods since stack trace evaluation is an expensive task.

Restrictions

The helper method should be only responsible for standard exception handling. Don't try to move all use case specific exception handling into the external helper method.

Conclusion

Lambda is generally a powerful feature to inject standard method with external functionality. This blog post showed only one out of many solutions to decorate a specific implementation with other functionalities.

No comments:

Post a Comment