Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Simple journal-based persistance for Clojure

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 src
Octocat-spinner-32 test
Octocat-spinner-32 .gitignore
Octocat-spinner-32 README
Octocat-spinner-32 project.clj
README
Simple Persistence for Clojure is a journal-based persistence library for Clojure programs. It follows "Prevalent system" design pattern.

The intended usage is assist you in making a prevalent system. Thus you work with your in-memory data and wrap every writing call into one of (apply-transaction*) macros.

Also it is possible to use it just as the journaling layer to record the (writing) transactions that happened to your system and replay them later.

Transactions are logged as a valid Clojure code, so they are easy to read and run separately. (Just wrap them into dosync)

Probably under certain conditions it can be a good fit for the prototyping stage of a project development.

However this pattern is used in production by some teams, see Prevayler mail list for details. Of course this implementation can contain bugs and must be tested well before doing that. Though it is very simple and I made some tests.

The disadvantage of the pattern is that your data structures must fit in memory (unless you implement ad-hoc paging solution). However the journaling nature lets you easily switch to other databases. For that you just re-implement your transaction functions (subjects of apply-transaction* ) to write/read from another DB and replay transactions one time (init-db).

Snapshotting is not implemented. It can solve another pattern problem - growing startup time.

In comparison with Prevayler, this library does not block the reads, because  it relies on Clojure STM. However it blocks the writes as Prevayler. To avoid this, atoms can be used to generate a transaction id without locking, but that will make reading and backup logic much more complex.

Probably blocking of writes is not important now, when the most of today computers have <=8 cores and the typical usage pattern is that there are an order of magnitude more reads than writes.

Usage examples:

1. first run

(use 'persister.core)

(def refx (ref 0))
(def refy (ref 0))
(def refs (ref {}))

(defn tr-fn [x y]
   (do
       (alter refx + x)
       (alter refy + y) ))

(defn tr-fn-swap []
   (let [tmp @refx]
       (ref-set refx @refy)
       (ref-set refy tmp)))

(defn tr-inc []
   (ref-set refx (inc @refx))
   (ref-set refy (inc @refy)) )

(defn tr-set-s [new-s]
    (ref-set refs new-s) )

(init-db)
(apply-transaction tr-fn 1 2)
(apply-transaction tr-fn 10 20)
(apply-transaction tr-fn-swap)
(apply-transaction tr-inc)
(apply-transaction tr-set-s {:a :bb "key" #{"val1" :val2}})
[refx refy refs]

[#<Ref@5976c2: 23> #<Ref@183e7de: 12> #<Ref@112e7f7: {:a :bb, "key" #{:val2 "val1"}}>]

2. the second run

(use 'persister.core)

(def refx (ref 0))
(def refy (ref 0))
(def refs (ref {}))

(defn tr-fn [x y]
   (do
       (alter refx + x)
       (alter refy + y) ))

(defn tr-fn-swap []
   (let [tmp @refx]
       (ref-set refx @refy)
       (ref-set refy tmp)))

(defn tr-inc []
   (ref-set refx (inc @refx))
   (ref-set refy (inc @refy)) )

(defn tr-set-s [new-s]
    (ref-set refs new-s) )

(init-db)
[refx refy refs]

[#<Ref@10fe2b9: 23> #<Ref@1ee148b: 12> #<Ref@186d484: {:a :bb, "key" #{:val2 "val1"}}>]


Note that journaled functions must be accessible in the current namespace when you replay transactions.

See inline doc for details.
Something went wrong with that request. Please try again.