Skip to content

Instantly share code, notes, and snippets.

@niquola
Created June 3, 2021 15:59
Show Gist options
  • Save niquola/be014c6a0760862ddf9912e6e5312efc to your computer and use it in GitHub Desktop.
Save niquola/be014c6a0760862ddf9912e6e5312efc to your computer and use it in GitHub Desktop.

zen lang

1. CRUD

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

2. Model (Metadata)

  • 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)

3. Data Formats

  • 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"}}}

4. Names and References

User(address:Address)-*Roles

{:entity User
 :attrs {:roles   {:ref Role}
         :address {:type Address}}}

5. Let's add an operation model

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

6. What if we want to publish/reuse models?

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 {...}
 }

}

8. Organize models into project

src/
 myapp/
   server.edn
   models.edn
libs/
  server.edn

Use "search paths" approach to load and re-use model libraries!

9. Classification & Tags

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) => [...]

10. Add schema to validate models?

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}}}

:confirms #{} (Open World assumption)

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.

Schema & Tags (Composition)

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}}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment