https://github.com/spagreeks/datomic.schema
https://github.com/spagreeks/datomic.schema
clojure datomic dsl spec
Last synced: 4 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/spagreeks/datomic.schema
- Owner: spagreeks
- License: epl-1.0
- Created: 2017-07-12T08:15:40.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2018-08-14T04:58:52.000Z (over 7 years ago)
- Last Synced: 2025-03-02T11:33:37.269Z (10 months ago)
- Topics: clojure, datomic, dsl, spec
- Language: Clojure
- Size: 47.9 KB
- Stars: 27
- Watchers: 2
- Forks: 3
- Open Issues: 1
-
Metadata Files:
- Readme: README.org
- License: LICENSE
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.