Where I’ve landed with my experimentation is something very similar to how symbols and namespaces work, except for keywords.
I have a defalias
function which works the same as alias
except under the hood it’ll call create-ns
to allow the alias to be created in the current code namespace:
(defalias 'foo 'com.bar.baz.foo)
;; => ::foo is now resolved to :com.bar.baz.foo in the current namespace
But I also created in-scope
which works like in-ns
except it creates an “alias scope”. So I can do:
(in-scope 'com.bar.baz)
(defalias 'foo)
;; => ::foo is now resolved to :com.bar.baz.foo in the current namespace
(in-scope 'com.fizz.buzz)
(defalias 'foo)
;; => ::foo is now resolved to :com.fizz.buzz.foo in the current namespace
So what I do now is I can have something like this:
(ns com.org.app.data-model ...)
(in-scope 'com.org.app)
(defalias 'user) ;; Means ::user will be an alias for com.org.app.user because that's the current scope
(s/def ::user/name string?) ;; ::user/name resolves to :com.org.app.user/name
(s/def ::user/email string?)
(s/def ::user/user
(s/keys :req [::user/name ::user/email]))
And in another file:
(ns com.org.app.core
(:require [com.org.app.data-model]))
;; Can use in-scope here as well for the default alias scope so you don't have
;; to repeat the full namespace in all defalias calls here.
(in-scope 'com.org.app)
(defalias 'user)
(defalias 'cart)
(defalias 'transaction)
;; If you have an entity name conflict, you can use the 2-ary variant
;; to alias it directly
(defalias 'other-user 'com.org.other-app.user)
(defn user->other-user [user] ...)
(s/fdef user->other-user
:args (s/cat :user [::user/user])
:ret ::other-user/user)