| Introduction |
| ------------------------------------------------------------ |
| |
| severin provides a Clojure API for implementing resource |
| pools, such as network and database connections. |
| |
| |
| Installation |
| ------------------------------------------------------------ |
| |
| This library can be installed from Clojars[1]. |
| |
| Leiningen/Boot |
| -------------- |
| |
| [zcfux/severin "0.3.0"] |
| |
| |
| Creating and releasing resources |
| ------------------------------------------------------------ |
| |
| Resources have an associated URI. They are created and |
| placed back in pool with create! and dispose!. |
| |
| (defn pool (make-pool)) |
| |
| (let [r (create! pool "monger://localhost")] |
| ; do something |
| (dispose! pool r)) |
| |
| with-pool evaluates a body in a try expression. Created |
| resources are bound to names. The finally clause calls |
| dispose! on each name. |
| |
| (with-pool pool [db "monger://localhost" |
| file "file:///var/log/out"] |
| ; do something |
| ) |
| |
| |
| Resource lifecycle |
| ------------------------------------------------------------ |
| |
| The lifecycle of every resource type is managed by a |
| factory. |
| |
| (defprotocol FactoryProtocol |
| (-create! |
| [this uri] |
| "Creates a new resource from a URI.") |
| |
| (-dispose! |
| [this resource] |
| "Disposes a resource.") |
| |
| (-recycle! |
| [this resource uri] |
| "Recycles an existing resource and assigns a URI.") |
| |
| (-valid? |
| [this resource] |
| "Tests if a resource is still valid.")) |
| |
| The make-factory multimethod creates a factory from a URI by |
| dispatching on the scheme. |
| |
| (defmulti make-factory |
| "Creates a factory from a URI by dispatching on the scheme." |
| #(-> % |
| java.net.URI. |
| .getScheme)) |
| |
| |
| Pool internal |
| ------------------------------------------------------------ |
| |
| A pool is a Ref holding a map. It can be created with |
| make-pool. |
| |
| Disposed resources are pushed onto a queue. Queues are |
| grouped by resource URIs. This association can be customized |
| by implementing the URI->KeyProtocol. |
| |
| (defprotocol URI->KeyProtocol |
| (-uri->key |
| [this uri] |
| "Converts a URI to a keyword.")) |
| |
| When implementing a pool for network connections like HTTP |
| you might want to group resources by hostname instead of |
| their URI. |
| |
| (defrecord HttpFactory |
| |
| [...] |
| |
| URI->KeyProtocol |
| (-uri->key |
| [this uri] |
| (-> uri |
| java.net.URI. |
| .getHost |
| keyword))) |
| |
| |
| Factory example |
| ------------------------------------------------------------ |
| |
| In this example we implement a pool for file input streams. |
| |
| (ns severin.example |
| (:require [severin.core :refer :all])) |
| |
| (defrecord FileReaderFactory [] |
| FactoryProtocol |
| (-create! |
| [this uri] |
| (let [resource (clojure.java.io/make-input-stream uri {})] |
| (.mark resource 0) |
| resource)) |
| |
| (-dispose! |
| [this resource] |
| (.close resource)) |
| |
| (-recycle! |
| [this resource uri] |
| (.reset resource) |
| resource) |
| |
| (-valid? |
| [this resource] |
| true)) |
| |
| (defmethod make-factory "file" ; this registers FileReaderFactory |
| [uri] |
| (FileReaderFactory.)) |
| |
| Creating a stream for the very first time a mark is set. The |
| cursor is positioned to the beginning of the file when a |
| resource is recycled. |
| |
| Let's create a pool and open a file. |
| |
| => (def pool (make-pool :max-size 5)) |
| => (def s (create! pool "file:///tmp/some/file")) |
| |
| Everything looks fine until you place back the resource in |
| pool. |
| |
| => (dispose! pool s) |
| => IllegalArgumentException Couldn't get URI from resource. |
| |
| What happened here? As described before factories are |
| created by dispatching on the scheme. Therefore you have to |
| specify the URI if it's not provided by the resource itself. |
| |
| => (dispose! pool "file:///tmp/some/file" s) |
| |
| You can fix this by adding a custom resource type which |
| implements URIProtocol. |
| |
| (ns severin.filereader |
| (:gen-class |
| :extends java.io.BufferedInputStream |
| :init init |
| :state state |
| :constructors {[String][java.io.InputStream]}) |
| (:require [severin.core :refer [URIProtocol -uri]])) |
| |
| (defn -init |
| [uri] |
| [[(-> uri |
| java.net.URI. |
| .getPath |
| clojure.java.io/as-file |
| java.io.FileInputStream.)] |
| uri]) |
| |
| (extend severin.filereader |
| URIProtocol |
| {:-uri #(.state %)}) |
| |
| After updating the factory you can dispose resources without |
| specifying the URI. |
| |
| => (dispose! pool s) |
| |
| If a resource doesn't implement URIProtocol severin tries to |
| lookup :uri. This makes it possible to use maps instead of |
| custom types. |
| |
| (defrecord FileReaderFactory |
| [] |
| FactoryProtocol |
| |
| (-create! |
| [this uri] |
| (let [stream (clojure.java.io/make-input-stream uri {})] |
| (.mark stream 0) |
| {:stream stream :uri uri})) |
| |
| [...]) |
| |
| |
| References |
| ------------------------------------------------------------ |
| [1]: Clojars |
| |
| |
| WWW |
| ------------------------------------------------------------ |
| master.zip |
| GitHub |