knockbox is an eventual-consistency toolbox for Clojure, and eventually the JVM in general. It's inspired by statebox and the paper A comprehensive study of Convergent and Commutative Replicated Data Types.
Databases like Riak let you trade consistency for availability. This means that you can have two conflicting values for a particular key. Resolving these conflicts is up to application-logic in the database clients. Certain data-types and operations are suited for automatic conflict-resolution. This project is a collection of these data-types and operations.
There is also a blog post about knockbox here.
knockbox is currently in development and the API will be changing quickly, without notice.
Each of the data type in knockbox implement the knockbox.resolvable/Resolvable
protocol, which is currently simply:
(resolve [a b]))
When it comes time to resolve sibling, you can resolve a vector of them like this:
;; notice the namespace difference
(knockbox.core/resolve [a b c d e])
The data types also implement any appropriate Java Interfaces and Clojure Protocols. The different knockbox set implementations, for example, can be used just like Clojure Sets.
(require 'knockbox.sets)
;; last-write-wins set
(def a (knockbox.sets/lww))
;; => #{}
;; two-phase set
(def b (knockbox.sets/two-phase))
;; => #{}
;; observed-remove set
(def c (knockbox.sets/observed-remove))
;; => #{}
(disj a :foo)
;; => #{}
(conj b :bar)
;; => #{:bar}
Registers are simple containers for values. Currently there is one Register implementation with last-write-wins semantics.
(require '(knockbox core registers))
;; the only argument to lww is the value
;; of the register
(def a (knockbox.registers/lww "1"))
(def b (knockbox.registers/lww "2"))
(def c (knockbox.registers/lww "3"))
;; the value can be queried like
(.value a)
;; => "1"
(.value (knockbox.core/resolve [c b a]))
;; => "3"
Maps are currently a work in progress, and don't yet implement all of the necessary Java intefaces.
Counters with an unbounded number of actors (ie. each of your JVM's + Pids) are a tough garbage collection problem. I haven't yet figured out a way to deal with this. Finding a way to bound the number of actors, and not doing GC may be another option.
knockbox currently supports JSON serialization for the three set types. Usage below:
(require '(knockbox sets core))
(def a (knockbox.sets/lww))
(def b (conj a :foo :bar :baz))
(def j (knockbox.core/to-json b))
(def c (knockbox.core/from-json j))
(= b c)
;; => true
Tests can be run by typing:
lein deps # this only needs to be done once
lein midje
Source documentation can be generated by typing:
lein deps
lein marg
lein deps
only needs to be run once.