https://github.com/20centaurifux/severin
A Clojure API for implementing resource pooling.
https://github.com/20centaurifux/severin
clojure pooling
Last synced: 5 days ago
JSON representation
A Clojure API for implementing resource pooling.
- Host: GitHub
- URL: https://github.com/20centaurifux/severin
- Owner: 20centaurifux
- License: gpl-3.0
- Created: 2017-09-03T18:56:35.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2023-02-25T09:30:31.000Z (over 3 years ago)
- Last Synced: 2025-11-22T03:11:03.076Z (8 months ago)
- Topics: clojure, pooling
- Language: Clojure
- Size: 72.3 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# severin
## Introduction
*severin* provides a Clojure API for implementing resource pools, such as
network and database connections.
## Installation
The library can be installed from Clojars using Leiningen:
[](https://clojars.org/zcfux/severin)
## 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:///tmp/helloworld.txt"]
; 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 internals
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 the full URI.
```
(defrecord
[...]
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"
[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)) ; queues can grow up to 5 resources
=> (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)
{:uri uri
::stream stream}))
[...])
```