An open API service indexing awesome lists of open source software.

https://github.com/spagreeks/datomic.schema


https://github.com/spagreeks/datomic.schema

clojure datomic dsl spec

Last synced: 4 months ago
JSON representation

Awesome Lists containing this project

README

          

* 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 "abc@example.xyz"
: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 "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

#+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.