datomic.schema
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.