Malli currently has two ways for re-using schemas (instances):
We can define Schemas using def:
(require '[malli.core :as m])
(def UserId :uuid)
(def Address
  [:map
   [:street :string]
   [:lonlat [:tuple :double :double]]])
(def User
  [:map
   [:id UserId]
   [:name :string]
   [:address Address]])
(def user
  {:id (random-uuid)
   :name "Tiina"
   :address {:street "Satakunnunkatu 10"
             :lonlat [61.5014816, 23.7678986]}})
(m/validate User user)
; => true
All subschemas as inlined as values:
(m/schema User)
;[:map
; [:id :uuid]
; [:name :string]
; [:address [:map
;            [:street :string]
;            [:lonlat [:tuple :double :double]]]]]
To support spec-like mutable registry, we'll define the registry and a helper function to register a schema:
(require '[malli.registry :as mr])
(defonce *registry (atom {}))
(defn register! [type ?schema]
  (swap! *registry assoc type ?schema))
(mr/set-default-registry!
 (mr/composite-registry
  (m/default-schemas)
  (mr/mutable-registry *registry)))
Registering Schemas:
(register! ::user-id :uuid)
(register! ::address [:map
                      [:street :string]
                      [:lonlat [:tuple :double :double]]])
(register! ::user [:map
                   [:id ::user-id]
                   [:name :string]
                   [:address ::address]])
(m/validate ::user user)
; => true
By default, reference keys are used instead of values:
(m/schema ::user)
; :user/user
We can recursively deref the Schema to get the values:
(m/deref-recursive ::user)
;[:map
; [:id :uuid]
; [:name :string]
; [:address [:map 
;            [:street :string] 
;            [:lonlat [:tuple :double :double]]]]]
Clojure Spec declared map specs should be of keysets only. Malli supports this too:
;; (╯°□°)╯︵ ┻━┻
(reset! *registry {})
(register! ::street :string)
(register! ::latlon [:tuple :double :double])
(register! ::address [:map ::street ::latlon])
(register! ::id :uuid)
(register! ::name :string)
(register! ::user [:map ::id ::name ::address])
(m/deref-recursive ::user)
;[:map
; [:user/id :uuid]
; [:user/name :string]
; [:user/address [:map 
;                 [:user/street :string] 
;                 [:user/latlon [:tuple :double :double]]]]]
;; data has a different shape now
(m/validate ::user {::id (random-uuid)
                    ::name "Maija"
                    ::address {::street "Kuninkaankatu 13"
                               ::latlon [61.5014816, 23.7678986]}})
; => true
Schemas can be defined as a ref->?schema map:
(def registry
  {::user-id :uuid
   ::address [:map
              [:street :string]
              [:lonlat [:tuple :double :double]]]
   ::user [:map
           [:id ::user-id]
           [:name :string]
           [:address ::address]]})
Using registry via Schema properties:
(m/schema [:schema {:registry registry} ::user])
; => :user/user
Using registry via options:
(m/schema ::user {:registry (merge (m/default-schemas) registry)})
Works with both:
(m/deref-recursive *1)
;[:map
; [:id :uuid]
; [:name :string]
; [:address [:map 
;            [:street :string]
;            [:lonlat [:tuple :double :double]]]]]
Here's a comparison matrix of the two different ways:
| Feature | Vars | Global Registry | Local Registry | 
|---|---|---|---|
| Supported by Malli | ✅ | ✅ | ✅ | 
| Explicit require of Schemas | ✅ | ❌ | ✅ | 
| Support Recursive Schemas | ✅ | ✅ | ✅ | 
| Decomplect Maps, Keys and Values | ❌ | ✅ | ✅ | 
You should pick the way what works best for your project.
My personal preference is the Var-style - it's simple and Plumatic proved it works well even with large codebases.
(-flatten-refs
 [:schema {:registry {::user-id :uuid
                      ::address [:map
                                 [:street :string]
                                 [:lonlat [:tuple :double :double]]]
                      ::user [:map
                              [:id ::user-id]
                              [:name :string]
                              [:address ::address]]}}
  ::user])
;[:map {:id :user/user}
; [:id [:uuid {:id :user/user-id}]]
; [:name :string]
; [:address [:map {:id :user/address}
;            [:street :string]
;            [:lonlat [:tuple :double :double]]]]]
(-unflatten-refs *1)
;[:schema {:registry {::user-id :uuid
;                     ::address [:map
;                                [:street :string]
;                                [:lonlat [:tuple :double :double]]]
;                     ::user [:map
;                             [:id ::user-id]
;                             [:name :string]
;                             [:address ::address]]}}
; ::user]
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs | 
| ← | Move to previous article | 
| → | Move to next article | 
| Ctrl+/ | Jump to the search field |