A DSL for Datomic schema definition
[datomic.schema "0.1.19"]
- Simplify schemas definition
- Simplify Database Functions definition
- Good readability
- Auto resolve schema dependencies
- Generate clojure.spec for schemas
(require '[datomic.schema :as schema :refer [defschema]])
(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 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))
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)
Assume we use peer lib.
@(d/transact
conn
[(->User {:name "isaac"
:email "[email protected]"
:accounts {:balance 3.0M}
:roles :vip})])
(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))])
(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}
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 withdefschema
, that in order to let those raw schemas managed byschema/install
.
If spec.alpha in your classpath, defschema
will also produce a spec.
(->> (->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
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.