shell/service.clj orchestrates validation, core logic, and persistence:
(defn create-product [this input]
(let [[valid? errors data] (validate ProductInput input)]
(if valid?
;; The clock belongs in the shell so the core stays deterministic and testable.
(let [now (java.time.Instant/now)
product (product-core/prepare-product data now)]
(persistence/create-product! (:repo this) product))
(throw (ex-info "Validation failed"
{:type :validation-error :errors errors})))))
now is supplied by the shell because time is an external dependency, not part of the business rule itself.
That split is deliberate: