Skip to content

Instantly share code, notes, and snippets.

@ferdinand-beyer
Created December 13, 2024 10:48
Show Gist options
  • Save ferdinand-beyer/a706c58c39088630c96c2338cc34846e to your computer and use it in GitHub Desktop.
Save ferdinand-beyer/a706c58c39088630c96c2338cc34846e to your computer and use it in GitHub Desktop.
Example DDD framework for Clojure + Datomic
(ns app.service
(:require [domain.user :as user]
[support.app :as app]))
(defn ensure-user-exists! [username full-name]
(let [ctx (app/new-context)]
(if-let [user (user/find-user-by-username ctx username)]
user
(let [ctx (-> ctx
(user/create-user username full-name)
(app/commit!))]
;; TODO: We could provide some mechanism to resolve tempids from previous
;; transaction.
(user/find-user-by-username ctx username)))))
(ns domain.user
(:require [datomic.api :as d]
[support.domain :as domain]))
(defn find-user-by-username [ctx username]
(d/pull (domain/db ctx) '[*] [:user/username username]))
(defn create-user [ctx username full-name]
(-> ctx
(domain/add-tx-data [{:user/username username, :user/full-name full-name}])
(domain/log (str "Created user '" username "'"))
(ns support.app
(:require [datomic.api :as d]
[support.domain :as domain]))
(defprotocol IUnitOfWork
(commit! [unit-of-work]))
(deftype LoggingContext [logs]
domain/ILogContext
(log [_ message]
(LoggingContext. (conj logs message)))
IUnitOfWork
(commit! [_]
(run! logging/log! logs)
(LoggingContext. []))
(defn- new-logging-context []
(LoggingContext. []))
(deftype DatomicContext [conn db tx-data]
domain/IDatomicContext
(db [_] db)
(add-tx-data [ctx new-tx-data]
(DatomicContext. inst logs conn db (into tx-data new-tx-data)))
IUnitOfWork
(commit! [_]
(let [{:keys [db-after]} @(d/transact conn tx-data)]
(DatomicContext. conn db-after [])))))
;; We assume that this gets initialised somewhere...
(def ^:dynamic *datomic-conn* nil)
(defn- new-datomic-context []
(let [conn *datomic-conn*]
(DatomicContext. conn (d/db conn) [])))
(deftype Context [inst log-ctx db-ctx]
domain/ITimeContext
(now [_] inst)
domain/ILogContext
(log [_ message]
(Context. inst (domain/log log-ctx message) db-ctx))
IDatomicContext
(db [_] (domain/db db-ctx))
(add-tx-data [ctx tx-data]
(Context. inst log-ctx (domain/add-tx-data db-ctx tx-data)))
IUnitOfWork
(commit! [_]
(Context. (java.time.Instant/now)
(commit! log-ctx)
(commit! db-ctx)))
(defn new-context []
(Context. (java.time.Instant/now)
(new-logging-context)
(new-datomic-context)))
(ns support.domain
"Infrastructure for the domain layer.")
(defprotocol ITimeContext
(now [ctx] "Returns the current time instant."))
(defprotocol ILogContext
(log [ctx message] "Logs a message."))
(defprotocol IDatomicContext
(db [ctx] "Returns the db value.")
(add-tx-data [ctx tx-data] "Adds tx-data to transact."))
;; Example of additional context protocols:
(defprotocol IAuthContext
(user [ctx] "Returns the authenticated user."))
;; Go-style context to support cancellation / deadlines
;; (but not pure)
(defprotocol ICancelableContext
(canceled? [ctx])
(deadline [ctx]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment