no raise api in java

Mon, Jun 27, 2011

I’ve been reading Exceptional Ruby by Avdi Grimm to get a different perspective on exception handling and was pleased to see he recommends what he calls namespaced exceptions. In Java this is just bundling your own Exception classes as part of the API:

public class MatrixException extends Exception {
}

public boolean doSomething(File theFile) throws MatrixException { try { … do something with a file } catch(FileNotFoundException fnfe) { throw new MatrixException(fnfe); } }

The API eats all the exceptions it thinks can occur and repackages them as MatrixException objects so the client doesn’t have to worry about any other exceptions. But this is more a philosophical point now. What exactly is an exception? It’s something exceptional. If your code meets an exceptional situation it raises its eyebrows and throws an exception. It shouldn’t really do this for unexceptional situations. The classic of the genre is the bad input from a user. That’s expected so your code should just sigh and get on with it, rather than run about waving its hands in the air.

I’m designing a backing store that can be used for failed accounts in a provisioning system. JMS messages come in off a topic, are acted upon to, among other things, create accounts in a system. If the account can’t be created, for example, the system to be provisioned is offline, the provisioner puts the message in the backing store and carries on with the rest of the messages. The first cut of the backing store threw MatrixExceptions left right and centre if anything went wrong, anywhere. You see, the backing store has two aspects. A database that can hold the failed messages and what I called the Last Chance Saloon (LSC) in case the database is unreachable. The LSC is just a directory on the server. Every now and then the provisioner will trawl both types of backing store reprocessing stored messages. The unit tests for the backing store all worked fine but the integration testing in the provisioner uncovered lots of problems, the most worrying being that the lack of a database masked out the LSC due to excessive exception throwing. The problem was basically down to namespaced exceptions not being ominiscient and letting this through:

com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException
when the database couldn’t be reached. That’s ok in itself but the caller then started throwing its arms and legs in the air too and the whole lot went to pot. Also, an exception thrown from the backing store constructor made it very difficult to test the different types of backing store in an integration scenario. Think Spring injection and init(). I couldn’t re-init a bean with a bum database backing store to let me get at the LSC aspect for testing as the resultant MatrixException when the database couldn’t be reached meant the already initialised backing store object wasn’t overwritten with the new one.

So I thought and read for a bit and came to the same conclusion as Avdi Grimm. Why not use a no-raise API for the backing store? Eat all exceptions internally and just say to the caller “yes I did that” or “no, I couldn’t do that” and let the caller deal with it. This is a good strategy I think as the only places where are exceptions are thrown are in the database handling code and only if the database is either offline or shagged. Is this an exceptional situation? A database offline or corrupted? I doubt it. Happens all the time if you ask me. So why throw exceptions and get everyone into a state of panic? If the backing store just returns false for storeMessage(…), let the caller deal with it. It will just use the LSC instead.

That’s another philosophical question though. Should the backing store automatically put a message in the LSC if the database is down? That’s an idea that’s looking very tempting. The ethos of the backing store is “I store messages for you”. Should the caller care where the messages are stored? Prolly not. In fact, the only time the caller should get in a panic is when the backing store can’t store messages at all. Then the caller is left holding a message it can’t process as the system to be provisioned is down and the caller has nowhere to persist the message.

In the case of all out panic such as a dead backing store the caller should really do some sort of transactional message consumption from the original ActiveMQ topic but that’s a totally different kettle of fish.

comments powered by Disqus