Home

Awesome

Aerospike-clj

An opinionated Clojure library wrapping Aerospike Java Client.

Clojars Project

Build Status

Docs:

Generated docs

Tutorial

here.

More advanced docs

Requirements

Features

Maturity

Opinionated

Usage

user=> (require '[aerospike-clj.client :as aero])
nil
user=> (def c (aero/init-simple-aerospike-client
  #_=>          ["aerospike-001.com", "aerospik-002.com"] "my-ns" {:enable-logging true}))

It is possible to inject additional asynchronous user-defined behaviour. To do that add an implementation of the ClientEvents protocol during client initialization or per operation.
Some useful info is passed in-order to support metering and to read client configuration. op-start-time is (System/nanoTime). see more here.

(let [c (aero/init-simple-aerospike-client
          ["localhost"]
          "test"
          {:client-events (reify ClientEvents
                            (on-success [_ op-name op-result index op-start-time]
                                (println op-name "success!")))
                            (on-failure [_  op-name op-ex index op-start-time]
                                (println "oh-no" op-name "failed on index" index)))})]

  (get-single c "index" "set-name"))
; for better performance, a `deftype` might be preferred over `reify`, if possible.

Query/Put

For demo purposes we will use a docker based local DB:

$ sudo docker run -d --name aerospike -p 3000:3000 -p 3001:3001 -p 3002:3002 -p 3003:3003 aerospike

And connect to it:

user=> (def c (aero/init-simple-aerospike-client ["localhost"] "test"))
#'user/db
user=> (require '[promesa.core :as p])
nil
user=> (aero/put c "index" "set-name" 42 1000)
#object[java.util.concurrent.CompletableFuture 0x6264b083 "pending"]
user=> (def f (aero/get-single c "index" "set-name"))
#'user/f
user=> (p/chain (aero/get-single c "index" "set-name")
  #_=>          :ttl
  #_=>          aero/expiry-unix
  #_=>          #(java.time.Instant/ofEpochSecond %)
  #_=>          str
  #_=>          println)
2020-08-13T09:52:49Z
#object[java.util.concurrent.CompletableFuture 0x654830f5 "pending"]

We actually get back a record with the payload, the DB generation and the TTL (in an Aerospike style EPOCH format).

user=> @(aero/get-single c "index" "set-name")
#aerospike_clj.client.AerospikeRecord{:payload 42, :gen 1, :ttl 285167713}

Unix EPOCH TTL

Aerospike returns a TTL on the queried records that is epoch style, but with a different "beginning of time" which is "2010-01-01T00:00:00Z". Call expiry-unix with the returned TTL to get a TTL relative to the UNIX epoch.

Testing

Unit tests

Executed via running lein test.

Integration tests

Testing is performed against a local Aerospike docker container.

Mocking in application unit tests

For unit tests purposes you can use a mock client that implements the client protocols: MockClient.

Usage:

(ns com-example.app 
  (:require [clojure.test :refer [deftest use-fixtures]]
            [aerospike-clj.protocols :as pt]
            [aerospike-clj.mock-client :as mock])
  (:import [aerospike_clj.client SimpleAerospikeClient]))

(def ^:dynamic ^SimpleAerospikeClient client nil)

(defn- bind-client-to-mock [test-fn]
  (binding [client (mock/create-instance)]
    (test-fn)))

(use-fixtures :each bind-client-to-mock)

(deftest ...) ;; define your application unit tests as usual

The sample code executes on every test run. It initializes the mock with a proper type hint so you can just invoke all client protocol methods on it.

Note: If the production client is initiated using a state management framework, you would also need to stop and restart the state on each test run.

Contributing

PRs are welcome with these rules:

License

Distributed under the Apache 2.0 License - found here.