Getting back into Clojure a bit and trying out Datomic as a possible database to transition to using for some projects. Datomic schemas are rather neat, as they are simply data-structures that you commit to the database. However, they can be rather verbose, so I’ve been using a simple preprocessing that uses inline-defined templates in a schema defintion for expansions. This keeps the schema definition with the templates still a simple data structure.

It only supports merging of maps, so cannot be used (as currently implemented) for defining higher level abstractions, but it does give you great flexibility to still override attributes specified by a template, and also to compose multiple templates together.

(defn extract-key [m k]
  (when-let [value (k m)] [(dissoc m k) value]))

(defn expand-entry-id [entry]
  (if (keyword? (:db/id entry))
    (update-in entry [:db/id] d/tempid)
    entry))

(defn expand-schema-entry [entry templates]
  (if (and (map? entry) (:meta/templates entry))
    (let [[entry template-refs] (extract-key entry :meta/templates)]
      (merge (apply merge (map templates template-refs)) entry))
    entry))

(defn preprocess-schema
  ; If passed a set of entries, return a processed set of entries
  ; Otherwise expect a {:schema :templates} map.
  ([schema]
    (if (map? schema)
      (preprocess-schema (:schema schema :templates schema))
      (:schema (preprocess-schema schema {}))))

  ([schema templates]
    (reduce
      (fn [res entry]
        (let [entry (expand-schema-entry entry (:templates res))]
          (if-let [[templ templ-name] (extract-key entry :meta/def-template)]
            (update-in res [:templates] assoc templ-name templ)
            (update-in res [:schema] conj (expand-entry-id entry)))))
      {:schema [] :templates {}}
      schema)))

And a simple example use (note that the :db/id should be just the partition it’s to be installed in, otherwise the reader will expand the id literal prematurely, resulting in duplicates):

[
 {:meta/def-template :string-attr
  :db/id :db.part/db
  :db/valueType :db.type/string
  :db/cardinality :db.cardinality/one
  :db.install/_attribute :db.part/db}

 {:meta/templates [:string-attr]
  :db/ident :user/forename
  :db/doc "The forename of the user."}
]

For the current code, an expanded example, and feedback, please see this gist: gist.github.com/4387703

(Updated 29/12/2012 to remove use of transients and atoms)


If you spot an error and would like to submit a correction, you can view the source for this post on GitHub.