Skip to content

6pages/datomic-utils

Repository files navigation

datomic-utils

Utilities for working with Datomic.

Clojars Project

Status

Running in production at 6Pages but unstable and subject to change. Let's call it an early release.

Features

The most interesting features in the library are:

  1. schema migrations
  2. query abstraction

schema migrations

Datomic recommends that we grow our schema and never break it. However, they leave it up to us (the user) to decide how to manage the migrations of schema accumulation.

This library includes a simple solution.

  • Your application code keeps a collection of schemas
  • com.6pages.datomic.schema stores an applied schema version number in Datomic
  • whenever you add new schemas (new schema version), com.6pages.datomic.schema/update! will make sure that all schema collections have been transacted up to the most recent version

Here's what a collection of schemas might look like:

[
 ;; version 0
 [
  ;; schema
  {:db/ident       :com.6pages.datomic.schema/version
   :db/valueType   :db.type/long
   :db/cardinality :db.cardinality/one}]

 ;; version 1
 [
  ;; Person
  {:db/ident       :person/id
   :db/valueType   :db.type/uuid
   :db/unique      :db.unique/identity
   :db/cardinality :db.cardinality/one}
  {:db/ident       :person/name
   :db/valueType   :db.type/string
   :db/cardinality :db.cardinality/one}]

 ;; version 2
 [
  ;; 
  {:db/ident       :person/friend
   :db/valueType   :db.type/ref
   :db/cardinality :db.cardinality/many}]
 ]

You must explicitly define the attribute storing the schema version in your first version (there's a validation exception if you forget).

As you need to add more schema definitions, you simply add another version collection and run com.6pages.datomic.schema/update! on all your databases.

query abstraction

Do you find yourself writing many queries for one (or more) attribute & value pairs?

(d/q
  db
  '[:find (pull ?e [*])
    :in $ ?id
    :where
    [?e ::id ?id]]
  person-id)

This library has some simple abstractions to build these types of queries.

(ns person
  (:require [com.6pages.datomic :as d]))
  
(def opts
  {:client (d/client {}) :db-name "dev"})
  
(d/p-> opts ['*] [[::id id]]) ;; single result

(d/p->> opts ['*] [[::name "Bob"]]) ;; collection of results

Usage

In a Clojure project

  1. add to dependencies

  2. require in a namespace

(ns person
  (:require 
    [com.6pages.datomic :as d]
    [com.6pages.datomic.schema :as ds]))
  1. build Datomic client options
(def opts
  {:db-name "dev"
   :client 
    (d/client 
    ;; use your own Datomic client config
    {:server-type :dev-local
     :system "datomic-samples"})})
  1. apply your schema
(def schemas [
  [{:db/ident       :com.6pages.datomic.schema/version
    :db/valueType   :db.type/long
    :db/cardinality :db.cardinality/one}]
  [{:db/ident       :person/id
    :db/valueType   :db.type/uuid
    :db/unique      :db.unique/identity
    :db/cardinality :db.cardinality/one}
   {:db/ident       :person/name
    :db/valueType   :db.type/string
    :db/cardinality :db.cardinality/one}]])
     
(ds/update! opts schemas)
  1. transact!
(def entity
  (d/transact!
    opts
    [{:person/id (java.util.UUID/randomUUID)
      :person/name "Ada"}]))

REPL and tests

  1. setup dev-local
  2. clj -A:local -A:dev (you may also want to add a REPL server, if you're into that sort of thing)

Related work

License

Copyright © 2021 6Pages Inc.

Distributed under the Eclipse Public License, the same as Clojure.