I've recently been exploring Datomic more seriously and have found myself jumping through the same hoops as I have in the past *just to get things up and running*. I've also encountered slight deficiencies in the documentation that I've had to re-investigate since the exploratory project I created was deleted quite a while ago.
I'm tired of retracing my same steps over and over again so I thought I'd create a quick post with some of the basic steps to get setup and using Datomic in a Clojure application.
This is just going to cover the basics. Datomic does some crazy things I haven't had a chance to try yet, like using rules, querying the database at a [particular instant in time](http://docs.datomic.com/tutorial.html#working-with-time), or getting a list of changes to an entity.
You do not need to install Datomic to get started, you can use the in-memory database.
Go here: https://my.datomic.com/downloads/free and find the latest version.
Then add [com.datomic/datomic-free "<the-latest-version>"]
to your Leiningen project.
This is pretty easy, but you should have something like this:
(ns my-project.components.datomic
(:require [com.stuartsierra.component :as component]
[datomic.api :as datomic]))
(defrecord DatomicComponent [uri conn]
component/Lifecycle
(start [this]
(if (:conn this)
this
(do
(assoc this :conn (datomic/connect uri)))))
(stop [this]
(assoc this :conn nil)))
Schema should be ideally be kept in an EDN file and loaded on demand:
(def schema
(delay
(read-string
(slurp (io/resource "my_project/schema.edn"))))
(defn create-schema [conn]
(datomic/transact conn @schema))
Here is what your schema might look like:
; resources/my_project/schema.edn
[
{:db/id #db/id[:db.part/db]
:db/ident :cake/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/fulltext true
:db/doc "The name of a cake"
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :cake/owner
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one
:db/doc "The owner of a cake"
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :user/email
:db/unique :db.unique/value
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "Email address of a user"
:db.install/_attribute :db.part/db}
{:db/id #db/id[:db.part/db]
:db/ident :user/phone-numbers
:db/valueType :db.type/string
:db/cardinality :db.cardinality/many
:db/doc "Contact numbers for a user"
:db.install/_attribute :db.part/db}
]
Information on defining your schema and all the options available is documented here.
Like your schema, any seed data should be kept in a separate file:
(def seed-data
(delay
(read-string
(slurp (io/resource "my_project/seed.edn"))))
(defn seed-db [conn]
(datomic/transact conn @seed-data))
Here is what your seed data might look like:
; resources/my_project/seed.edn
[
;; Users
{:db/id #db/id[:db.part/user -1000001]
:user/email "sally@test.com"
:user/password "supersecret"
:user/phones ["8469481047", "9471038596"]}
{:db/id #db/id[:db.part/user -1000002]
:user/email "bob@test.com"
:user/password "secret"
:user/phones ["1234567890", "0987654321"]}
;; Cakes
{:db/id #db/id[:db.part/user]
:cake/owner #db/id [:db.part/user -1000001]
:cake/name "Carrot"}
{:db/id #db/id[:db.part/user]
:cake/owner #db/id [:db.part/user -1000001]
:cake/name "Cheese"}
{:db/id #db/id[:db.part/user]
:cake/owner #db/id [:db.part/user -1000002]
:cake/name "Carrot"}
]
The operations to be performed on an entity should be confined to it's own namespace:
(ns my-project.users
(:require [datomic.api :as datomic]
[my-project.component.datomic])
; Note the change from using a dash to an underscore
(:import [my_project.component.datomic DatomicComponent]))
(defprotocol UserOps
(all [this])
(by-email [this email])
(save! [this user])
(extend-type DatomicComponent
UserOps
(all [this]
(datomic/q '[:find [(pull ?user [*]) ...]
:where [?user :user/email]]
(datomic/db (:conn this))))
(by-email [this email]
(datomic/q '[:find [(pull ?user [*])]
:in $ ?email
:where [?user :user/email ?email]]
(datomic/db (:conn this))
email))
(save! [this user]
(datomic/transact (:conn this) user)
The [(pull ?user [*]) ...]
is an example of Datomic's pull syntax. This basically says "after all ?user
s, bring in all of their attributes. Be careful when using the wildcard *
as this will recursively pull any component attributes.
It's possible to pull in refs
by specifying them in the pull pattern. If you wanted cakes with their owners:
(datomic/q '[:find [(pull ?cake [* {:cake/owner [*]}]) ...]
:where [?cake :cake/owner]]
db)
If however you wanted the reverse, users and their cakes:
(datomic/q '[:find [(pull ?user [* {:cake/_owner [*]}]) ...]
:where [?user :user/email]]
db)
This is one I haven't found a good real world use case for yet, but it is possible. Read the following if you're looking at doing these kinds of queries:
http://docs.datomic.com/query.html#rules
http://hashrocket.com/blog/posts/using-datomic-as-a-graph-database