Taking a break from Fake for a moment (I’ll get back to it, I promise!)
Note: this post assumes a basic understanding of computation expressions in F#; if you’re looking for a primer on them,
http://en.wikibooks.org/wiki/F_Sharp_Programming/Computation_Expressions is a good place to start.
Note 2: the code in this post is actually broken if you use a use! or try finally block within your audit block. Check out https//blog.mavnn.co.uk/corrected-error-handling-computational-expres for the corrected code.
I had a series of small services I needed to add error handling to today to polish them up from nice clean prototype to production ready status.
Thinking about the joys of re-implementing the same error handling strategy in each of them, it occurred to me that this was exactly the kind of thing that computational expressions were built for. But my requirements went a bit further than just log the error and continue: each of these services contain critical locations where if a error occurred, we didn’t just want it logged locally, but we wanted (in some cases) an alert sent to a remote auditing* server.
Of course, mocking out the auditing service for testing is a bit of a pain in the backside, and how auditing is achieved shouldn’t really be the responsibility of these services anyway.
So, I decided to experiment with a builder interface.
Meet the IErrorHandlerBuilder interface:
Should look awfully familiar if you’ve recently checked out the msdn page on computational expressions:
http://msdn.microsoft.com/en-us/library/dd233182.aspx (except, you know, mine has the right signatures; that might want to be fixed at some point).
How do we use it?
Well, first we need an implementation. In fact, let’s have two. Our first candidate, the AuditBuilder, takes an unhealthy number of dependencies that (in turn) take an unhealthy number of dependencies that you almost certainly want to supply from an IoC container.
What does it do? Well, basically if any bound function throws it logs the error via log4net and our in house audit server and then returns None. Otherwise it returns Some result from the computation. Obviously, if a computation further up the chain has already thrown we already have a None and we just pass it along. TryFinally and Using are implemented to make sure that you can still use disposable resources as you would expect within the expression.
Our second candidate is the TestErrorBuilder:
This is almost identical to the implementation above, except that it takes no dependencies at all and if there is an error it just logs to the console via printfn. Very useful for debugging - you might even want a unit test version that logs nothing, depending on your unit tests.
And now, our preparation is complete. How do we use our new toy, you ask?
Something like this:
This little service persists interesting email messages to a document store, and sends the contents back to you when you request them. If an email is not persisted, or a request for an email fails, we want a remote alert to be triggered in production. Both the persist and content requests are handled as RabbitMq messages (using the EasyNetQ client).
So in the message handler callbacks, declare an audit { } block and let! bind our message. Then we simply pass that bound message on to a handling function or object and return the resulting value, safe in the knowledge that our hosting program will have supplied an appropriate IErrorHandlerBuilder for our current purposes. The beauty of all of this is that the service object does not need to know what the error handling strategy is, and the objects/functions doing the work do not need to worry about error handling at all.
This technique is especially useful in situations where you are making use of asynchronous programming techniques such as agents and you really don’t want a single failure taking out your whole agent or async expression. If for some reason it’s important to your code what the error is, as well as knowing it’s been handled, it would be easy to modify this technique to return a Choice or custom MaybeError discriminated union that you could pull the exception out of later.
It would also be helpful for managing things like SQL connections with multiple retry attempts: for testing you may not want that kind of complexity and you could pass in a simpler strategy builder, while testing your robust, multiple retry computational builder for use in actual deployed environments (test or production).
And, of course, just knowing that builder interfaces are possible raises up a whole new world of possibilities.
As always, I tend to write about stuff that is fresh and new to me (i.e. not fully considered and maybe fatally flawed). Comments and suggestions welcome.
* To avoid any confusion, I should note that the word audit is used loosely in this blog post, with a meaning more related to a namespace within our code than the normal meaning of the word ‘audit’.