hodur-org / hodur-engine

Hodur is a domain modeling approach and collection of libraries to Clojure. By using Hodur you can define your domain model as data, parse and validate it, and then either consume your model via an API or use one of the many plugins to help you achieve mechanical results faster and in a purely functional manner.
MIT License
282 stars 13 forks source link

Qualified names #18

Open madstap opened 4 years ago

madstap commented 4 years ago

When using hodur I ran into an issue with names.

(def meta-db
  (engine/init-schema
   '[^{:spec/tag-recursive true
       :datomic/tag-recursive true}
     tx
     [^Integer amount]]))

(hodur-spec/defspecs meta-db {:prefix :foo}) ;=> [:foo.tx/amount :foo/tx]

(hodur-datomic/schema meta-db) ;=>
[#:db{:ident :tx/amount,
      :valueType :db.type/long,
      :cardinality :db.cardinality/one}]

The amount attribute is defined as :foo.tx/amount when it's a spec, and just :tx/amount in the db. I want to have the same identifier for the same attribute everywhere in my clojure code, so amount would be called :foo.tx/amount everywhere. I also ran into this same issue when writing a plugin to generate a json (de)serializer.

(def meta-db
  (engine/init-schema
   '[^{:spec/tag-recursive true
       :datomic/tag-recursive true
       :json-serde/tag-recursive true}
     tx
     [^Integer amount]]))

(hodur-spec/defspecs meta-db {:prefix :foo}) ;=> [:foo.tx/amount :foo/tx]

;; When I write my own plugin it needs the prefix arg
(def parse-json
  (hodur-json/parser meta-db :tx {:prefix :foo}))

;; To use the same qualified name everywhere, I wrote a wrapper
(def schema
  (my-hodur-datomic-wrapper/schema meta-db {:prefix :foo}))

Now I need to pass the prefix a bunch of places in the code. It would be better to specify the prefix together with the rest of the data model.

I think most plugins are going to want to refer to a canonical, unique, qualified name for each attribute, so I feel like this should be the responsibility of hugin-engine.

The api I want is the following:

(def meta-db
  (engine/init-schema
   '[^{:spec/tag-recursive true
       :datomic/tag-recursive true
       :json-serde/tag-recursive true
       :ns-prefix/tag-recursive :foo}
     tx
     [^Integer amount]]))

(hodur-spec/defspecs meta-db) ;=> [:foo.tx/amount :foo/tx]

(def parse-json
  (hodur-json/parser meta-db :foo/tx))

(parse-json "{\"amount\":10}") ;=> {:foo.tx/amount 10}

(def schema
  (hodur-datomic/schema meta-db))
;; =>
[#:db{:ident :foo.tx/amount,
      :valueType :db.type/long,
      :cardinality :db.cardinality/one}]

When no ns-prefix is specified, default to the current namespace, like the spec plugin.

(ns my.app
  (:require [hodur-engine.core :as engine]))

(def meta-db
  (engine/init-schema
   '[^{:spec/tag-recursive true}
     tx
     [^Integer amount]]))

(hodur-spec/defspecs meta-db) ;=> [:my.app.tx/amount :my.app/tx]

On the meta-api side, this would involve adding a :<node-type>/qualified-name to each entity.

{:type/qualified-name  :foo/tx}
{:field/qualified-name :foo.tx/amount}
{:param/qualified-name :foo.tx.nested/param}

The downside to all this is that it would be a breaking change for the datomic plugin and maybe others.

What do you think? How would you structure the namespaces?

madstap commented 4 years ago

Actually doesn't need to be a breaking change to anything if you don't default to the current namespace.

{:ns-prefix/tag true} can mean the current namespace.