Skip to content

Clojure client for the Aerospike database.

License

Notifications You must be signed in to change notification settings

sol-sh/aerospike-clj

 
 

Repository files navigation

Aerospike-clj

An opinionated Clojure library wrapping Aerospike Java Client.

Clojars Project

Build Status

Docs:

Generated docs

Tutorial:

here.

More advanced docs:

Requirements:

  • Java 8
  • Clojure 1.8

Features:

  • Converts Java client's callback model into Java(8) CompletableFuture based API.
  • Expose passing functional (asynchronous) transcoders over payloads (both put/get).
  • Health-check utility.
  • Functions return Clojure records.

Stability:

  • stable. Although not 1.x, the API will remain stable.

Maturity:

  • Feature completeness: mostly near complete.
  • Stability: production ready. Actively and widely used in production.

Opinionated:

  • Non blocking only: Expose only the non-blocking API. Block with deref if you like.
  • Futures instead of callbacks. Futures (and functional chaining) are more composable and less cluttered. If a synchronous behaviour is still desired, the calling code can still deref (@) the returned future object. For a more sophisticated coordination, a variety of control mechanisms can be used by directly using Java's CompletableFuture API or the more Clojure friendly promesa (which is also used internallly), or via the library using transcoders or hooks.
  • Follows the method names of the underlying Java APIs.
  • TTLs should be explicit, and developers should think about them. Forces passing a TTL and not use the cluster default (This can be still achieved by passing the special values -2,-1 or 0).
  • Minimal dependencies.
  • Single client per Aerospike namespace. Namespaces in Aerospike usually indicate different cluster configurations. In order to reduce overhead for clusters with more than a single namespace create 2 client objects and share an event loop between them.

Limitations/ caveats

TBD

  • Support Java 11

Usage:

Most of the time just create a simple client (single cluster)

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 instance of ClientEvents. Some useful info is passed in in-order to support metering and to read client configuration. op-start-time is (System/nanoTime) 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 db]
                              (when (:enable-logging? db)
                                (println op-name "success!")))
                            (on-failure [_  op-name op-ex index op-start-time db]
                              (println "oh-no" op-name "failed on index" index)))})]

  (get-single c "index" "set-name"))
; for better performence, 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 UNIX TTL if you want to convert it later to a more standard timestamp.

Testing

Testing is performed against a local Aerospike running in the latest docker

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

Mocking in application unit tests

When performing unit tests in application code, it is most times undesirable to launch a full Aerospike container to run tests against. For those cases the library exposes a mock client that replaces all the calls to aerospike-clj.client.

Usage:

(ns com-example.app 
  (:require [clojure.test :refer [deftest use-fixtures]]
            [aerospike-clj.mock-client :refer [init-mock]]))

(use-fixtures :each init-mock)

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

The sample code executes on every test run. It initializes the mock and runs the test within a with-redefs - rebinding all the calls to functions in aerospike-clj.client to the mock.

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!

License

Distributed under the Apache 2.0 License - found here.

About

Clojure client for the Aerospike database.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Clojure 99.4%
  • Shell 0.6%