create_user(db, attrs)
validate_user(attrs)
exec("insert into user (colls..) value {{attrs}}")
notify_created(attrs)
Generalize! But we need also tables, docs, separated validation, js sdk...
- annotations
- code analyzis
- put it as a data
user.yaml
entity: User
table: users
attrs:
name: {type: string, required: true}
password: ...
code:
create(cfg, attrs)
validate(cfg, attrs)
exec("insert into {{cfg.table}} ({{cfg.cols}}) value {{attrs}}")
notify(cfg, attrs)
validate(cfg, attrs)
generate_docs(cfg)
generate_database(cfg)
generate_sdk(cfg)
- xml - should die
- yaml* - readable, crazy parser
- json* - well known, too limited, boilerplate
- edn! - less known, nice balance between yaml and json
edn = json - syntax noise, + data types, tags and comments
Additional types:
- symbol - just.some/name
- keyword - :keyword, or :my-ns/keyword
- set #{1 4 5}
Tags:
- #inst "1985-04-12T23:20:50.52Z"
- #uuid "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
example.edn
{:entity User ;; symbol
:attrs {:name {:type "string"}
:password {:type "string"}}}
User(address:Address)-*Roles
{:entity User
:attrs {:roles {:ref Role}
:address {:type Address}}}
operations.edn
{:type rest/operation
:path "/User"
:method "POST""
:body {:schema User}} <- here we want to compose two models
Can we have a one place to put all models?! Let's organize it as a code:
models/
user.edn
routes.edn
Use global names. Introduce packages or namespaces.
{ns myapp.user
import #{entity server}
User {
:type entity/entity
:attrs {...}
}
create-user {
:type server/operation
:body {:schema myapp.user/User}
:response {...}
}
}
src/
myapp/
server.edn
models.edn
libs/
server.edn
Use "search paths" approach to load and re-use model libraries!
Ok we put all entities into data, now we want to generate all tables. I.e. get all entities. Let's label all models with "tags" and implement search by tag
{ns myapp.user
import #{entity server}
User {
:zen/tags #{entity/Entity}
:attrs {...}
}
Role {
:zen/tags #{entity/Entity}
:attrs {...}
}
}
get-symbol('myapp.user/User) get-by-tag('entity/Entity) => [...]
Data DSL to describe shape of data as a data.
-
json-schema (akward global names & refs, keywords semantic, closed world)
-
graphql (custom specialized syntax)
-
protobuf (too limited)
-
CUE (custom syntax)
===ZEN SCHEMA===
:type => enable keywords, each keyword is a rule!
User {
:zen/tags #{zen/schema}
:type zen/map
:require #{:name :password}
:keys {:name {:type zen/string
:min-length 5
:regex "^[A-Z].**"}
:birthDate {:type zen/datetime
:in-future true}}}
Resource {
:zen/tags #{zen/schema}
:type zen/map
:keys {:id {:type zen/string}
:meta {...}}}
User {
:zen/tags #{zen/schema}
:confirms #{Resource} <<-
:type zen/map
:keys {:name {:type zen/string}
:birthDate {:type zen/datetime}}}
Each schema is stand-alone. Confirm this schema and other schemas from confirms±! About unknown keys in a map - all schemas are collaborate. Schema validates only keys - it knows! If key was not recognized by any schema -> error.
If schema attached to tag, entity with tag validated against this schema
;; db.edn
persistent {
:zen/tags #{zen/tag zen/schema}
:keys {:db/table {:type zen/string}}
}
;; zdoc.edn
persistent {
:zen/tags #{zen/tag zen/schema}
:keys {:zdoc/comments {:type zen/string}}
}
;; myapp.edn
User {
:zen/tags #{zen/schema db/persistent zdoc/entity}
:type zen/map
:db/table "users"
:zdoc/comments "User is a ...."
:keys {:name {:type zen/string}
:birthDate {:type zen/datetime}}}