Steve and I have been working on a book for the last few months entitled Growing Object-Oriented Software, Guided by Tests. We're now at the stage where we can put content online to garner feedback. We'll post a chapter every week or so. Eventually, the book will appear as a Rough Cut on Safari.
If you'd like to read and comment, please join the Yahoo group we've set up for discussion about the content. We'd love to hear what you think.
Two of my favourite conferences are looking for submissions.
|XP Day is looking for experience reports, tutorials and structured workshops to run in a track alongside the more freeform lightning talks and open space that will make up the rest of the conference. This year the focus of the conference will advancing the state of the art, rather than introducing Agile or Scrum or XP or TDD.|
|SPA 2009 is looking for session proposals which are interactive and leading edge, possibly even experimental. They can be about technology or teams, practice or process - in fact anything to do with improving software development.|
I think both conferences are excellent places for first-time presenters to run a session. New presenters will get a lot of help from experienced presenters as they go through the shepherding process. And this year, SPA is offering a free place to the best proposal from a new presenter.
Here's yet more blather about exception handling. The last one, I promise! (For now...)
My last post describes how I like to coordinate exception handling inside the components of a distributed system. A component handles the failure of other remote services that it uses — database servers, for example — by sending back appropriate error responses or rolling back distributed transactions. That's all well and good when a component runs within an application server, because the server handles all the messy details of fail-over and reconnection for you. But what about "main apps": plain old Java programs that run from a main method?
It can take a lot of code to correctly handle connection failure and reconnection, exponentially back-off connection attempts, clean up long-lived objects that hold onto connections, and hide all the messy technical details away from the business logic behind domain-term interfaces. It's also hard to get all the corner cases right.
It's much easier to just not bother with reconnection at all.
When a main app catches an EnvironmentException it should roll back transactions, send back response codes, or whatever it needs to do and then, instead of trying to reconnect, just die. Launch the app from a supervisor process that restarts it whenever it dies. The Java Service Wrapper does the job very nicely.
Now you don't need to write any reconnection logic at all. The application's start-up code is enough.
This greatly simplifies writing distributed Java apps. And a simple system is more reliable, easier to secure and easier to change.
I'm on a bit of a roll when it comes to exception handling tips, so here's another technique that's worked well in the last few systems I've had a hand in, this time for coordinating exception handling within a component of a distributed system.
A component in a distributed system receives requests from remote components and reacts by making requests of its own to remote components in its environment that it depends on, before sending back a response. However, because its clients and dependencies are out of its control, it cannot guarantee that the requests it receives are correct or that the services it depends on are available when it must service a request. By "request" I mean either a client-server style request/response interaction or an asynchronous event received from a message broker.
In Java terms, because both bad requests and failed dependencies are out of the component's control, they should be reported by throwing checked exceptions. However, the way they should be handled is significantly different. A bad request should never be retried, but could be logged for manual repair and replay if that makes sense for the system. The failure of a dependency is (hopefully) temporary, and so the request can be retried later.
Apart from RMI, Java frameworks for writing distributed systems don't make this distinction in the exceptions they throw or handle. Therefore, when building distributed systems, among the first things I write are two base exception classes: BadRequestException and EnvironmentException. Depending on the communication protocol, the application will handle these in different ways:
|Bad Request Exception||Environment Exception|
|HTTP (e.g. Servlets)||Return a 4xx response code||Return a 5xx response code|
|JMS (e.g. Message Driven Beans)||Move message to a hospital queue and commit the transaction.||Roll back the transaction, leaving the message on the input queue for later redelivery|
Because frameworks don't make the distinction between Bad Requests and Environment Exceptions, I keep the framework code — servlet or MDB class, for example — as thin as possible, doing little more than delegating the request to an interface that throws BadRequestException or EnvironmentException and handling each kind of error as appropriate.
The client-side code of an synchronous remote call needs to translate the status it receives appropriately. If the returned status indicates that the client made a bad request (e.g. an HTTP 4xx code), it should throw a RuntimeException to indicate that a programming error has been detected. If the status indicates an environment exception (e.g. an HTTP 5xx code), it should throw a checked exception so the compiler ensures that the exception is handled. I usually wrap that logic up in a convenient proxy object.