datomic.schema icon indicating copy to clipboard operation
datomic.schema copied to clipboard

  • datomic.schema A DSL for [[http://www.datomic.com/][Datomic]] schema definition

** Installation

[[http://clojars.org/datomic.schema/latest-version.svg]]

#+begin_src clojure [datomic.schema "0.1.19"] #+end_src

** Features

  • Simplify schemas definition
  • Simplify Database Functions definition
  • Good readability
  • Auto resolve schema dependencies
  • Generate clojure.spec for schemas

** Usage #+begin_src clojure (require '[datomic.schema :as schema :refer [defschema]]) #+end_src

*** Get Started #+begin_src clojure

  (defschema User
    (schema/attrs
     [:name :string]))

  ;; Construction Usage
  (->User {:name "isaac"})
  ;;=> {:user/name "isaac"}

  ;; Spec Usage
  (require '[clojure.spec.alpha :as s])
  (s/valid? User {:user/name "isaac"})

  ;; Install schemas
  (schema/install datomic-peer-connection-or-client-connection)

#+end_src

Now, let's do some more complicated stuff.

*** Defschema =(defschema Foo ...)= do two things - Define a Clojure record =Foo=, store the datomic's schemas, etc. - Define a constructor =->Foo=

#+begin_src clojure
  (declare Account UserRole)

  ;; User with 4 attributes
  ;;   `:user/name`
  ;;   `:user/email`    with `:db.unique/identity`
  ;;   `:user/accounts` is   `:db.type/ref`, ref many Accounts
  ;;   `:user/roles`    is   `:db.type/ref`, ref many Roles
  (defschema User
    (schema/attrs
     [:name     :string]
     [:email    :string    {:unique :identity}]
     [:accounts #'Account  {:cardinality :many}]
     [:roles    #'UserRole {:cardinality :many}]))

  (defschema Account
    ;; new partition `:db.part/account`, for Account constructor
    ;; partition must working with datomic peer library
    (schema/partition :db.part/account)
    (schema/attrs
     [:balance :bigdec]
     ;; `:foo/bar` already qualified,
     ;; ignore the schema namespace `account`
     [:foo/bar :string]))

  ;; Enumerations: `:user.type/vip`, `:user.type/blacklist` ...
  ;; We specify namespace to `:user/type` instead of `:user.role`
  (defschema UserRole
    (schema/namespace :user.type)
    (schema/enums :vip :blacklist :normal))

#+end_src

*** Install attributes =schema/install= both support client connection and peer connection #+begin_src clojure ;; only install attributes of User (schema/install conn User)

  ;; install all defined attributes, `schema/install` with no argument
  (schema/install conn)
#+end_src

*** Transact & Construct Assume we use peer lib. #+begin_src clojure

  @(d/transact
      conn
      [(->User {:name     "isaac"
                :email    "[email protected]"
                :accounts {:balance 3.0M}
                :roles    :vip})])

#+end_src

*** Functions #+begin_src clojure (defschema User (schema/attrs [:name :string])

    ;; database function `:fn.user/valid?`
    (schema/fn valid? [u]
      (assert (-> (:user/name u) (count) (< 30))
              "`:user/name` must shorter than 30 characters")
      u)

    ;; transact function, db as first argument, but name is qualified already
    (schema/fn :user/add [db u]
      [(d/invoke db :fn.user/valid? u)]))


  ;; We also can gather all database function into one schema
  (defschema Functions
    (schema/fn :fn.user/new [name]
      {:user/name name})

    (schema/fn :fn.user/greet [u]
      (str "greeting " (:user/name u))))

  ;; ok, you may be want to transact functions directly
  @(d/transact conn [(schema/fn :abc/foo [args]
                       (prn args))])
#+end_src

*** Schema dependencies

#+begin_src clojure

  (defschema Species
    (schema/attrs
     [:parent #'Species])
    (schema/enums
     :animal
     {:db/ident :bird
      :parent   :species/animal}))


#+end_src

That will produce three datomic schemas like belowing. In this case, the third(=:species/bird=) schema depends on previous two schemas, it's fine, this is considered by the =schema/install=.

#+begin_src clojure

  ;; one attribtes
  {:db/ident              :species/parent
   :db/valueType          :db.type/ref
   :db/cardinality        :db.cardinality/one
   :db.install/_attribute :db.part/db}

  ;; `:species/animal`
  {:db/ident              :species/animal}

  ;; `:species/bird`
  {:db/ident              :species/bird
   :species/parent        :species/animal}

#+end_src

*** Raws api Sometimes, you just want to attach a raw datomic schema to schema-record. It's fine, let's do it:

#+begin_src clojure

  (defschema RawSchemas
    (schema/raws
     {:db/ident :db/doc
      :db/doc   "use for write documentation of some entity"}))

  ;; more complicated
  (defschema SelfDepends
    (schema/attrs
     [:foo #'SelfDepends])
    (schema/raws
     {:db/doc "hello"}
     {:db/id            :self.depends/foo
      :self.depends/foo :self.depends/foo}))

#+end_src

#+begin_quote
You may curiously why =schema/raws= need co-working with =defschema=, that in order to let those raw schemas managed by =schema/install=.
#+end_quote

*** Schema as spec If [[https://github.com/clojure/spec.alpha][spec.alpha]] in your classpath, =defschema= will also produce a spec.

#+begin_src clojure

  (->> (->User {:name "isaac"
                :email "[email protected]"
                :roles  [:vip]})
       (s/valid? User))
  ;;=> true


  (->> {:user/name "isaac"
        :user/email "[email protected]"
        ;; for datomic, `:db.cardinality/many` also support single value
        :user/roles :user.role/vip}
       (s/valid? User))
  ;;=> true


  (->> {:user/name  "isaac"
        ;; will fail, because `:user/email` is `:db.cardinality/one`
        :user/email ["[email protected]"]
        :user/roles :user.role/vip}
       (s/valid? User))
  ;;=> false

#+end_src

** Differences from [[https://github.com/SparkFund/spec-tacular][spec-tacular]]

  • schema installation support both peer & client.
  • auto resolve dependencies of schemas for installation.
  • generate spec for schemas.
  • simple, no util functions except =schema/install=, [[https://github.com/SparkFund/spec-tacular][spec-tacular]] provide more utilities. IMO, the datomic.api is good enough.