Yesterday I came to the realisation that my clojure.spec specifications were still not simple enough, so I have now refactored them.
After experimenting in the REPL, I have a clearer understanding of how the namespace autoresolve works with composite specifications like :customer/details
. Using autoresove for the namespaces of the composite types uses the source code namespace, not the namespace of the composite specification.
The following spec is defined in the practicalli.data.relational
namespace.
(spec/def :customer/details
(spec/keys
:req [::legal-name ::email-address ::residential-address ::social-security-number]
:opt [::preferred-name]))
(spec-gen/generate (spec/gen :customer/details))
;; => #:practicalli.data.relational{:id #uuid "64561b61-7f33-4700-80d4-de9c8e4c9ced", :legal-name "gx5DbXIoOA8a06u837", :email-address "2M6691M9886U7sI", :residential-address "6c23GWul3Fk1nl1807H42UEHgUqt6", :social-security-number "DOAiLgjYcSHI"}
I also realised that I had an unnecessary abstraction when using both ::customer-details
and ::account-holder
, as these two specifications only differed in one value, the ::account-holder-id
. It seems obvious in hindsight that these two specs are two states of customer, :customer/unregistered
and :customer/registered
.
;; Customer detail specifications
(spec/def :customer/id uuid?)
(spec/def :customer/legal-name string?)
(spec/def :customer/email-address string?)
(spec/def :customer/residential-address string?)
(spec/def :customer/social-security-number string?)
(spec/def :customer/preferred-name string?)
;; Data to send to the database
(spec/def :customer/unregistered
(spec/keys
:req [:customer/legal-name
:customer/email-address
:customer/residential-address
:customer/social-security-number]
:opt [:customer/preferred-name]))
;; Data received from the database
(spec/def :customer/registered
(spec/keys
:req [:customer/legal-name
:customer/email-address
:customer/residential-address
:customer/social-security-number]
:opt [:customer/preferred-name]))
I have used specific namespaces for the keywords in the specifications to simplfy them, but also because I cannot seem to create a database table with a matching namespace to the fully qualified specification (as least not with H2 database).
The database is only for this application, so additional abstractions would be just adding complexity for no benefit.I've switched over to Fira Code font in my Clojure editor for the next few broadcasts, any feedback on readability is appreciated. Fira Code also includes font Ligature support, so there will be a few stylized characters. I dont think this will cause confusion, but if so please let me know.
I finally have my unit test working with the redesigned database schema and clojure.specs, generating data from the :customer/details specification and checking the result against the :customer/id specification.
(deftest new-customer-test
(is (spec/valid? :customer/id
(:id (SUT/new-customer
(spec-gen/generate (spec/gen :customer/unregistered))))))
)
I will walk through all the changes and improvements Saturday morning....I'll tidy up the code and push to GitHub in the morning