Skip to content

gfZeng/datomic.schema

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

datomic.schema

A DSL for Datomic schema definition

Installation

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

[datomic.schema "0.1.19"]

Features

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

Usage

(require '[datomic.schema :as schema :refer [defschema]])

Get Started

(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)

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
(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))

Install attributes

schema/install both support client connection and peer connection

;; only install attributes of User
(schema/install conn User)

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

Transact & Construct

Assume we use peer lib.

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

Functions

(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))])

Schema dependencies

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

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.

;; 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}

Raws api

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

(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}))

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

Schema as spec

If spec.alpha in your classpath, defschema will also produce a spec.

(->> (->User {:name "isaac"
              :email "abc@example.xyz"
              :roles  [:vip]})
     (s/valid? User))
;;=> true


(->> {:user/name "isaac"
      :user/email "abc@example.xyz"
      ;; 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 ["abc@example.xyz"]
      :user/roles :user.role/vip}
     (s/valid? User))
;;=> false

Differences from 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, spec-tacular provide more utilities. IMO, the `datomic.api` is good enough.