Closeable Services
In my book “Clojure Polymorphism” I use a service as an example for creating a polymorphic abstraction. The service is imagined to be a key-value blob store, like S3, Cloudfiles, or Azure.
I try as much as possible to focus the book on features that are compatible with both Clojure and ClojureScript, but here is a tip that is only useful in Clojure.
The main function in my example changes slightly for each implementation approach, but the core of it remains the same: we let the initialized service then enter a try
block which closes the service in a finally
block. Using a finally
block means that the service will get closed whether the try
block exits normally or via exception, and this is the safest way to ensure resources are cleaned up.
For this pattern Clojure has a built-in macro called with-open
. However, you can only use with-open
with an object that implements the java.io.Closeable
interface. An advantage with defining your abstraction using a protocol (as opposed to plain functions or multimethods) is you can also implement java.io.Closeable
for your service object:
Then the main function can be rewritten to:
The downside is there is no way to enforce this in the abstraction. Each implementation of the abstraction must individually implement java.io.Closeable
. It would be possible if you were using the “Protocol + Client Namespace + Multimethod” approach to wrap the object as it gets returned from connect in a Closeable
object, but that is the closest you could get:
I find this ugly, because not implementing java.io.Closeable
is a programmer mistake. If you were to use with-open
with an object that does not implement java.io.Closeable
, you would get an exception, you would go add that interface to the appropriate service implementation, you would double check all the other service implementations, and from that point on forevermore you would not get the exception anymore. Over the lifetime of your application maybe you would write 5-10 implementations of this service abstraction? Do you need to add a bunch of lines of code and an extra layer of run-time construct just because a programmer accidentally wrote the wrong code? The tradeoff doesn’t make sense to me.
What does make sense to me is taking advantage of platform interfaces and conventions to save myself time and lines of code. So if you’re implementing a service abstraction on the JVM throw a java.io.Closeable
in there.