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.

(defn-main[&args];; ... initialize some things ...
(let[access-key(System/getenv"AWS_ACCESS_KEY_ID")secret-key(System/getenv"AWS_SECRET_ACCESS_KEY")storage-conn(storage/connectaccess-keysecret-key)](try;; ... initialize some more things, or maybe just do stuff ...
(finally(storage/closestorage-conn)))))

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:

(defn-main[&args];; ... initialize some things ...
(let[access-key(System/getenv"AWS_ACCESS_KEY_ID")secret-key(System/getenv"AWS_SECRET_ACCESS_KEY")](with-open[storage-conn(storage/connectaccess-keysecret-key)];; ... initialize some more things, or maybe just do stuff ...
)))

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.