Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
[ai.obney.grain.event-store-v3.interface :as event-store]
[ai.obney.grain.command-processor-v2.interface.schemas :as command-schema]
[ai.obney.grain.anomalies.interface :refer [anomaly?]]
[ai.obney.grain.time.interface :as time]
[com.brunobonacci.mulog :as u]
[cognitect.anomalies :as anom]
[malli.core :as mc]
[malli.error :as me]))

(defn ->command
[command]
(merge {:command/id (random-uuid)
:command/timestamp (time/now)}
command))

(defn execute-command
[handler {:keys [event-store tenant-id] :as context}]
(let [result (try
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
(ns ai.obney.grain.command-processor-v2.interface
(:require [ai.obney.grain.command-processor-v2.core :as core]))

(defn ->command
"Returns command with :command/id and :command/timestamp filled in
when not already present."
[command]
(core/->command command))

(def command-registry* (atom {}))

(defn register-command!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,11 @@
;; Test Helpers

(defn make-command
[command-name & {:keys [id timestamp extra-data]
:or {id (uuid/v4)
timestamp (OffsetDateTime/now)}}]
(merge {:command/name command-name
:command/id id
:command/timestamp timestamp}
extra-data))
[command-name & {:keys [id timestamp extra-data]}]
(cp/->command
(cond-> (merge {:command/name command-name} extra-data)
id (assoc :command/id id)
timestamp (assoc :command/timestamp timestamp))))

(defn make-context
[command command-registry]
Expand Down Expand Up @@ -134,6 +132,32 @@
(is (= {:status "completed"} (:command/result result)))
(is (true? (:command-result/success result))))))

(deftest ->command-test
(testing "->command fills in :command/id and :command/timestamp when absent"
(let [command (cp/->command {:command/name :example/create-counter
:name "Counter A"})]
(is (uuid? (:command/id command)))
(is (instance? OffsetDateTime (:command/timestamp command)))
(is (= :example/create-counter (:command/name command)))
(is (= "Counter A" (:name command)))))

(testing "->command preserves a caller-supplied :command/id and :command/timestamp"
(let [id (uuid/v4)
ts (OffsetDateTime/now)
command (cp/->command {:command/name :example/create-counter
:command/id id
:command/timestamp ts})]
(is (= id (:command/id command)))
(is (= ts (:command/timestamp command)))))

(testing "a command built with ->command round-trips through process-command"
(let [command (cp/->command {:command/name :test/create-resource})
registry (make-registry {:test/create-resource successful-handler})
context (make-context command registry)
result (cp/process-command context)]
(is (not (contains? result ::anom/category)))
(is (true? (:command-result/success result))))))

(deftest test-command-with-events
(testing "Events are persisted to event store when present in result"
(let [command (make-command :test/create-with-events)
Expand Down Expand Up @@ -508,8 +532,8 @@
parent-handler (fn [context]
(let [child-ctx (assoc context :command-processor/skip-event-storage true)
child-result (cp/process-command
(assoc child-ctx
:command (make-command :test/skip-storage-child)))]
(assoc child-ctx
:command (make-command :test/skip-storage-child)))]
{:command-result/events (:command-result/events child-result)
:command/result {:aggregated true}}))
command (make-command :test/skip-storage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
(:require [ai.obney.grain.anomalies.interface :refer [anomaly?]]
[ai.obney.grain.command-processor-v2.interface.schemas :as command-schema]
[ai.obney.grain.command-processor-v2.interface :as cp]
[ai.obney.grain.time.interface :as time]
[clojure.core.async :as async]
[com.brunobonacci.mulog :as u]
[cognitect.anomalies :as anom]
Expand Down Expand Up @@ -50,9 +49,9 @@

(defn decode-command
[command]
(-> command
(assoc :command/id (random-uuid))
(assoc :command/timestamp (time/now))))
;; Always (re)generate :command/id and :command/timestamp server-side, ignoring
;; any client-supplied values, by dropping them before ->command fills them in.
(cp/->command (dissoc command :command/id :command/timestamp)))

(defn prep-response
[response]
Expand Down
69 changes: 31 additions & 38 deletions development/src/example_app_demo.clj
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
;;
(service/stop service)

""
)
"")


(comment
Expand All @@ -39,40 +38,36 @@
(try
(cp/process-command
(assoc context
:command {:command/name :example/create-counter
:command/timestamp (time/now)
:command/id (random-uuid)
:name "Counter A"}))
:command (cp/->command {:command/name :example/create-counter
:name "Counter A"})))
(catch Exception e (ex-data e)))

(into [] (es/read event-store {:tenant-id tenant-id}))

(def counters
(->> (qp/process-query
(assoc context
:query {:query/name :example/counters
:query/timestamp (time/now)
:query/id (random-uuid)}))
:query/result))
@(def counters

@bpringe bpringe May 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added @ for convenience - so the value of counters is returned when this form is evaluated.

(->> (qp/process-query
(assoc context
:query {:query/name :example/counters
:query/timestamp (time/now)
:query/id (random-uuid)}))
:query/result))


(def counter
(->> (qp/process-query
(assoc context
:query {:query/name :example/counter
:query/timestamp (time/now)
:query/id (random-uuid)
:counter-id (:counter/id (first counters))}))
:query/result))
@(def counter
(->> (qp/process-query
(assoc context
:query {:query/name :example/counter
:query/timestamp (time/now)
:query/id (random-uuid)
:counter-id (:counter/id (first counters))}))
:query/result))



(cp/process-command
(assoc context
:command {:command/name :example/increment-counter
:command/timestamp (time/now)
:command/id (random-uuid)
:counter-id (:counter/id counter)}))
:command (cp/->command {:command/name :example/increment-counter
:counter-id (:counter/id counter)})))


;; Projects the :example/counters read model (read-model-processor-v2).
Expand All @@ -83,8 +78,7 @@
(into [] (es/read event-store {:tenant-id tenant-id}))


""
)
"")

(comment
;; Interact with the service via HTTP
Expand All @@ -101,15 +95,15 @@
(catch Exception e (ex-data e)))

;; Get all counters
(def counters
(try
(:body
(http/post
"http://localhost:8080/query"
{:content-type :transit+json
:as :transit+json
:form-params {:query {:query/name :example/counters}}}))
(catch Exception e (ex-data e))))
@(def counters
(try
(:body
(http/post
"http://localhost:8080/query"
{:content-type :transit+json
:as :transit+json
:form-params {:query {:query/name :example/counters}}}))
(catch Exception e (ex-data e))))



Expand Down Expand Up @@ -143,5 +137,4 @@



""
)
"")
Loading