https://github.com/appsflyer/aerospike-clj
Clojure client for the Aerospike database.
https://github.com/appsflyer/aerospike-clj
Last synced: 7 months ago
JSON representation
Clojure client for the Aerospike database.
- Host: GitHub
- URL: https://github.com/appsflyer/aerospike-clj
- Owner: AppsFlyer
- License: other
- Created: 2019-01-06T11:20:31.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2025-08-20T10:50:05.000Z (8 months ago)
- Last Synced: 2025-09-16T15:01:09.788Z (7 months ago)
- Language: Clojure
- Size: 372 KB
- Stars: 29
- Watchers: 13
- Forks: 22
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Aerospike-clj
An opinionated Clojure library wrapping Aerospike Java Client.
[](https://clojars.org/com.appsflyer/aerospike-clj)
[](https://github.com/AppsFlyer/aerospike-clj/actions)
# Docs:
[Generated docs](https://appsflyer.github.io/aerospike-clj/)
## Tutorial
[here.](https://appsflyer.github.io/aerospike-clj/tutorial.html)
## More advanced docs
* [Advanced asynchronous hooks.](https://appsflyer.github.io/aerospike-clj/advanced-async-hooks.html)
# Requirements
- Java 8
- Clojure 1.8
- Aerospike server version >= `4.9.0`
- Clojure version >= `1.11.0`
# 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.
# 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 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](https://github.com/funcool/promesa) (which is also used internally),
or via the library using [transcoders](https://appsflyer.github.io/aerospike-clj/index.html) or
[hooks](https://appsflyer.github.io/aerospike-clj/advanced-async-hooks.html).
- 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](https://www.aerospike.com/apidocs/java/com/aerospike/client/policy/WritePolicy.html#expiration) -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 instances and share an event
loop between them.
## Usage
```clojure
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](https://appsflyer.github.io/aerospike-clj/advanced-async-hooks.html).
```clojure
(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:
```shell
$ sudo docker run -d --name aerospike -p 3000:3000 -p 3001:3001 -p 3002:3002 -p 3003:3003 aerospike
```
And connect to it:
```clojure
user=> (def c (aero/init-simple-aerospike-client ["localhost"] "test"))
#'user/db
```
```clojure
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).
```clojure
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:
```clojure
(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:
* A PR should increment the project's version in [`project.clj`](project.clj) according
to Semantic Versioning.
* A PR should have its above version set to `SNAPSHOT`, e.g. `1.0.2-SNAPSHOT`.
Once it will be merged into `master` this suffix would be trimmed before release.
* All PRs would be linted and tested. Passing lint and tests is a reuirement for
maintainers to review the PR.
## License
Distributed under the Apache 2.0 License - found [here](https://github.com/AppsFlyer/aerospike-clj/blob/master/LICENSE).