by Yuppiechef

Yuppiechef / cqrs-server

An opinionated Clojure CQRS/ES implementation using Onyx, Datomic, DynamoDB, Kafka and Zookeeper.

204 Stars 13 Forks Last release: Not found MIT License 29 Commits 0 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:


An opinionated CQRS/ES implementation using Onyx, Datomic, DynamoDB, Kafka and Zookeeper.


A quick guide to get started :

Install dynamodb local

Get dynamodb local from: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html

And then run:

java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar

Kafka & Zookeeper

Download and extract Kafka: http://kafka.apache.org/downloads.html

Run both these (probably in separate terminals)

bin/zookeeper-server-start.sh config/zookeeper.properties
bin/kafka-server-start.sh config/server.properties

Currently, this needs the latest SNAPSHOT version of Prismatic Schema, so clone that repo and install the jar:

git clone https://github.com/Prismatic/schema.git
cd schema
lein cljx once
lein install


Then clone this repo and fire it up!

cd cqrs-server
lein repl

=> (start) "Setup complete" => (send-command :user/register {:name "Bob" :age 31}) nil => (d/q '[:find [?e ...] :where [?e :user/name]] (d/db (d/connect datomic-uri))) [17592186045422] => (map #(d/touch (d/entity (d/db (d/connect datomic-uri)) %)) *1) ({:base/uuid #uuid "54d8fc2e-6c1f-4fb6-93f9-bef9536a9f7d", :user/age 31, :user/name "Bob", :db/id 17592186045422})

Now we have a user in the system, let's fill out his profile a bit:

=> (send-command :user/update-email {:uuid #uuid "54d8fc2e-6c1f-4fb6-93f9-bef9536a9f7d" :email "[email protected]"})
=> (send-command :user/disabled {:uuid #uuid "54d8fc2e-6c1f-4fb6-93f9-bef9536a9f7d"})
=> (map #(d/touch (d/entity (d/db (d/connect datomic-uri)) %)) (d/q '[:find [?e ...] :where [?e :user/name]] (d/db (d/connect datomic-uri))))
({:base/uuid #uuid "54d90a89-0880-4f30-bb34-42f29ceb1095", :user/age 31, :user/email "[email protected]", :user/name "Bob", :user/status :user.status/disabled, :db/id 17592186045422})

We can also send some pageviews and see how it updates the viewcount on the user (a possibly useful aggregate):

=> (send-command :user/pageview {:uuid #uuid "54d90a89-0880-4f30-bb34-42f29ceb1095" :url "http://www.example.com" :render-time 230})
=> (send-command :user/pageview {:uuid #uuid "54d90a89-0880-4f30-bb34-42f29ceb1095" :url "http://www.example.com" :render-time 212})
=> (send-command :user/pageview {:uuid #uuid "54d90a89-0880-4f30-bb34-42f29ceb1095" :url "http://www.example.com" :render-time 182})
=> (map #(d/touch (d/entity (d/db (d/connect datomic-uri)) %)) (d/q '[:find [?e ...] :where [?e :user/name]] (d/db (d/connect datomic-uri))))
({:base/uuid #uuid "54d90a89-0880-4f30-bb34-42f29ceb1095", :user/age 31, :user/email "[email protected]", :user/name "Bob", :user/status :user.status/disabled, :user/viewcount 3, :db/id 17592186045422})

Then lets have a look at the events:

=> (far/scan dynamodb-cred :events)
[{:date 1423510307575N, :data #, :basis-t 1008N, :id "86439637-8f1e-5170-9b23-824486e3506a", :type "user/pageviewed"} {:date 1423510178427N, :data #, :basis-t 1005N, :id "c67ccc74-c71c-5578-80ad-924c470f052f", :type "user/email-updated"} {:date 1423510316827N, :data #, :basis-t 1010N, :id "08316c9b-3fcd-5a9f-b095-4bf0c1a61a05", :type "user/pageviewed"} {:date 1423510210618N, :data #, :basis-t 1007N, :id "46ac00c9-bd7d-5903-91e0-af56d28ef751", :type "user/disabled"} {:date 1423510153513N, :data #, :basis-t 1000N, :id "be856c9c-0bf8-5ccc-bec1-bfa0f5a7e983", :type "user/registered"} {:date 1423510312463N, :data #, :basis-t 1009N, :id "5c2eb804-1016-5fa3-a868-c01b515f980d", :type "user/pageviewed"}]

The actual data is fressian encoded so that there's no pain with the transformation of clojure data structures.

NOTE: If you do actually use this for user aggregates and authentication, remember to at least bcrypt your passwords. Be very aware that sensitive data is written to multiple places in this system: the kafka queues, the dynamo event source and the datomic aggregate. This is a particularly important consideration for things like credit card details and passwords.


The default profile uses datomic-free - if you want to use datomic-pro, start the repl with

lein with-profile prod

When you use cqrs-server as a library to your application project, you'll probably want to add it as a dependency with an exclusion:

[cqrs-server "0.1.0-SNAPSHOT" :exclusions [com.datomic/datomic-free]]


Because this project depends on correct integration with kafka, dynamodb and zookeeper, the tests require that you have these running locally before you run

lein test
. See above in order to get them installed and started.


Copyright © 2015 Yuppiechef Online (Pty) Ltd.

Distributed under The MIT License (MIT) - See LICENSE.txt

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.