-
Notifications
You must be signed in to change notification settings - Fork 3
/
README
91 lines (60 loc) · 3.16 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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.