Skip to content

Instantly share code, notes, and snippets.

@mkrcah
Last active March 4, 2024 20:02
Show Gist options
  • Save mkrcah/8fac30c365069b6d1ab9466c04b9f470 to your computer and use it in GitHub Desktop.
Save mkrcah/8fac30c365069b6d1ab9466c04b9f470 to your computer and use it in GitHub Desktop.
(defn execute-operation! [operation]
(println "Executing " operation)
(case (:kind operation)
:call-fakturoid
(call-fakturoid! (:request operation))
:save-xml-to-file
(save-xml-to-file! (:xml operation) (:file operation))))
(defn generate-souhrnne-hlaseni [{:as bag :input/keys [year-month]}
{:as operation :keys [kind result]}]
(case kind
:start
(let [req (get-invoices-req)
full-req (full-fakturoid-req fakturoid-config req)]
(assoc bag
:fakturoid/req req
:fakturoid/full-req full-req
:operation {:kind :call-fakturoid
:request full-req}))
:call-fakturoid
(let [invoices (->> result :body (map extract-invoice) doall)
sh-lines (souhrne-hlaseni-lines invoices)
sh-lines-for-year-month (->> sh-lines
(filter #(= year-month (:issued-on/year-month %))))
xml (to-souhrnne-hlaseni-xml sh-lines-for-year-month)]
(assoc bag :fakturoid/raw result
:fakturoid/invoices invoices
:souhrnne-hlaseni/lines sh-lines
:souhrnne-hlaseni/for-year-month sh-lines-for-year-month
:souhrnne-hlaseni/xml xml
:operation {:kind :save-xml-to-file
:file (output-fname year-month)
:xml xml}))
:save-xml-to-file
(assoc bag :operation {:kind :done})))
(ns fiddle.think-do-assimilate-v1)
;; Some artificial process to try things on:
; 1. Get a name from Google API
; 2. Get a name from a command-line
; 3. Compare names:
; 4. If identical, send email
; 5. If different, send Kafka message
;; v1 - Let's try to do a tracer-bullet
; Aggressively thin IO
(defn call-google-api! [req]
(println "Calling google api" req)
{:message "success"
:some-data {:name "Johny"}})
(defn read-input-from-command-line! [prompt]
(println "Reading input from command-line, prompt:" prompt)
{:cli-input "John"})
(defn send-kafka-msg! [msg]
(println "Sending Kafka message" msg)
{:status :succeeded})
(defn send-email! [email]
(println "Sending email" email)
{:http-code 200
:detail "Email sent"})
; Extractors
(defn name-from-google [google-response]
(-> google-response :some-data :name))
(defn name-from-cli [input]
(-> input :cli-input))
(defn google-req [] {:method :get :path "/name"})
; Enriching the bag
(defn with-name-from-google [bag]
(let [request (google-req)
response (call-google-api! request)]
(assoc bag :google {:response response
:request request
:name (name-from-google response)})))
(defn with-name-from-cli [{:as bag}]
(let [input (read-input-from-command-line! "enter name")]
(assoc bag :cli {:name (name-from-cli input)})))
; Thinking
(defn names-equal? [bag]
(= (:name :google bag) (:name :cli bag)))
(defn with-compared-names [bag]
(assoc bag :names-equal? (names-equal? bag)))
; Some thinking and doing (design tension increasing)
(defn email-or-kafka! [bag]
(if (:names-equal? bag)
(let [body {:subject (str "Same name " (:name :google bag))}
response (send-email! body)]
(assoc bag :email {:body response
:response response}))
(let [msg {:event :different-names-detected
:google-value (:google/name bag)
:user-value (:cli/name bag)}
response (send-kafka-msg! msg)]
(assoc bag :kafka {:msg msg
:response response}))))
(comment
(-> {}
with-name-from-google
with-name-from-cli
with-compared-names
email-or-kafka!))
;{:google/request {:method :get, :path "/name"},
; :google/response {:message "success", :some-data {:name "Johny"}},
; :google/name "Johny",
; :cli/name "John",
; :names-equal? false,
; :result/response {:status :succeeded},
; :result/op "send-kafka"}
(ns fiddle.think-do-assimilate-v2)
;; Some artificial process to try things on:
; 1. Get a name from Google API
; 2. Get a name from a command-line
; 3. Compare names:
; 4. If identical, send email
; 5. If different, send Kafka message
;; v2 - Let's try to purify it according to https://clojuredesign.club/episode/112-purify/
;; Doing (aggressively thin single-point-of-entry IO)
(defn call-google-api! [req]
(println "Calling google api" req)
{:message "success"
:some-data {:name "Johny"}})
(defn read-input-from-command-line! [prompt]
(println "Reading input from command-line, prompt:" prompt)
{:cli-input "John"})
(defn send-kafka-msg! [msg]
(println "Sending Kafka message" msg)
{:status :succeeded})
(defn send-email! [email]
(println "Sending email" email)
{:http-code 200
:detail "Email sent"})
(defn execute-operation! [operation]
(assoc operation
:result
(case (:kind operation)
:send-email
(send-email! (:email-body operation))
:send-kafka
(send-kafka-msg! (:kafka-msg operation))
:get-name-from-google
(call-google-api! (:request operation))
:read-from-cli
(read-input-from-command-line! (:prompt operation)))))
;; Thinking
(defn names-equal? [bag]
(= (-> bag :google/name) (-> bag :cli/name)))
(defn email-body [bag]
{:subject (str "Same name " (:google/name bag))})
(defn kafka-msg [bag]
{:event :different-names-detected
:google-value (-> bag :google/name)
:user-value (-> bag :cli/name)})
(defn google-req []
{:method :get :path "/name"})
(defn determine-operation [bag]
(cond
(nil? (:google/name bag)) {:kind :get-name-from-google
:request (google-req)}
(nil? (:cli/name bag)) {:kind :read-from-cli
:prompt "Enter your name"}
(and (names-equal? bag)
(nil? (:email/response bag))) {:kind :send-email
:email-body (email-body bag)}
(and (not (names-equal? bag))
(nil? (:kafka/msg bag))) {:kind :send-kafka
:kafka-msg (kafka-msg bag)}
:else {:kind :done
:result {:status :succeeded
:context bag}}))
; Assimilating
(defn name-from-google [google-response]
(:name (:some-data google-response)))
(defn name-from-cli [input]
(:cli-input input))
(defn update-context [bag {:keys [kind result] :as operation}]
(case kind
:get-name-from-google
(assoc bag :google/request (:request operation)
:google/response result
:google/name (name-from-google result))
:read-from-cli
(assoc bag :cli/response result
:cli/name (name-from-cli result))
:send-email
(assoc bag :email/body (:email-body operation)
:email/response result)
:send-kafka
(assoc bag :kafka/msg (:kafka-msg operation)
:kafka/response result)))
; Orchestration
(defn orchestration-fn []
(loop [bag {}]
(println bag)
(let [operation (determine-operation bag)]
(if (= :done (:kind operation))
(:result operation)
(let [result (execute-operation! operation)]
(recur (update-context bag result)))))))
(comment
(determine-operation {:google/name "Johny"})
(determine-operation {:google/name "John"
:cli/name "John"})
(determine-operation {:google/name "John"
:cli/name "John"
:email/response "Email sent"})
(orchestration-fn))
;{:status :succeeded,
; :context {:google/request {:method :get, :path "/name"},
; :google/response {:message "success", :some-data {:name "Johny"}},
; :google/name "Johny",
; :cli/response {:cli-input "John"},
; :cli/name "John",
; :kafka/msg {:event :different-names-detected, :google-value "Johny", :user-value "John"},
; :kafka/response {:status :succeeded}}}
(ns fiddle.think-do-assimilate-v2)
;; v3 - Build on top of v2, tackling the coherence struggle
; Rationale:
; - preserve aggresively thin IO that is on the periphery
; - preserve purity of thinking and assimilating
; - but tackle the coherence struggle by merging the thinking
; and assimilating into a single pure function
;; Doing (aggressively thin single-point-of-entry IO)
(defn call-google-api! [req]
(println "Calling google api" req)
{:message "success"
:some-data {:name "Johny"}})
(defn read-input-from-command-line! [prompt]
(println "Reading input from command-line, prompt:" prompt)
{:cli-input "John"})
(defn send-kafka-msg! [msg]
(println "Sending Kafka message" msg)
{:status :succeeded})
(defn send-email! [email]
(println "Sending email" email)
{:http-code 200
:detail "Email sent"})
(defn execute-operation! [operation]
(assoc operation
:result
(case (:kind operation)
:send-email
(send-email! (:email-body operation))
:send-kafka
(send-kafka-msg! (:kafka-msg operation))
:get-name-from-google
(call-google-api! (:request operation))
:read-from-cli
(read-input-from-command-line! (:prompt operation)))))
;; Extractors and transforms
(defn names-equal? [bag]
(= (-> bag :google/name) (-> bag :cli/name)))
(defn email-body [bag]
{:subject (str "Same name " (:google/name bag))})
(defn kafka-msg [bag]
{:event :different-names-detected
:google-value (-> bag :google/name)
:user-value (-> bag :cli/name)})
(defn google-req []
{:method :get :path "/name"})
(defn name-from-google [google-response]
(:name (:some-data google-response)))
(defn name-from-cli [input]
(:cli-input input))
(defn find-names [bag {:as operation :keys [kind result request]}]
"Given result of an operation, update the context bag and determine the next operation"
(case kind
:start
(assoc bag :operation {:kind :get-name-from-google
:request (google-req)})
:get-name-from-google
(assoc bag :google/request request
:google/response result
:google/name (name-from-google result)
:operation {:kind :read-from-cli
:prompt "Enter your name"})
:read-from-cli
(assoc bag :cli/response result
:cli/name (name-from-cli result)
:operation (if (names-equal? bag)
{:kind :send-email
:email-body (email-body bag)}
{:kind :send-kafka
:kafka-msg (kafka-msg bag)}))
:send-email
(assoc bag :email/body result
:email/response result
:operation {:kind :done
:result {:status :succeeded
:context bag}})
:send-kafka
(assoc bag :kafka/msg result
:kafka/response result
:operation {:kind :done})))
(defn some-other-flow [{:as bag :keys [required-emails]}
{:as operation :keys [kind result request]}]
"Try to send :required-emails, send Kafka msg on completion"
(case kind
:start
(assoc bag :emails-sent 0
:operation {:kind :send-email
:email-body "email #0"})
:send-email
(let [emails-sent (inc (:emails-sent bag))]
(assoc bag :emails-sent emails-sent
:event-log (conj (:event-log bag) operation)
:operation (if (< emails-sent required-emails)
{:kind :send-email
:email-body (str "email #" emails-sent)}
{:kind :send-kafka
:kafka-msg "All mails sent"})))
:send-kafka
(assoc bag :event-log (conj (:event-log bag) operation)
:operation {:kind :done})))
(defn execute [init-bag worker decider]
(loop [bag init-bag
operation {:kind :start}]
(let [updated-bag (decider bag operation)
next-operation (-> updated-bag :operation)]
(if (= :done (-> next-operation :kind))
updated-bag
(recur updated-bag (worker next-operation))))))
(comment
(execute {} execute-operation! find-names)
;{:operation {:kind :done},
; :google/request {:method :get, :path "/name"},
; :google/response {:message "success", :some-data {:name "Johny"}},
; :google/name "Johny",
; :cli/response {:cli-input "John"},
; :cli/name "John",
; :kafka/msg {:status :succeeded},
; :kafka/response {:status :succeeded}} {:operation {:kind :done},}
;; :google/request {:method :get, :path "/name"},
;; :google/response {:message "success", :some-data {:name "Johny"}},
;; :google/name "Johny",
;; :cli/response {:cli-input "John"},
;; :cli/name "John",
;; :kafka/msg {:status :succeeded},
;; :kafka/response {:status :succeeded}}
(execute {:required-emails 5} execute-operation! some-other-flow))
;{:required-emails 5,
; :emails-sent 5,
; :operation {:kind :done},
; :event-log ({:kind :send-kafka, :kafka-msg "All mails sent", :result {:status :succeeded}}
; {:kind :send-email, :email-body "email #4", :result {:http-code 200, :detail "Email sent"}}
; {:kind :send-email, :email-body "email #3", :result {:http-code 200, :detail "Email sent"}}
; {:kind :send-email, :email-body "email #2", :result {:http-code 200, :detail "Email sent"}}
; {:kind :send-email, :email-body "email #1", :result {:http-code 200, :detail "Email sent"}}
; {:kind :send-email, :email-body "email #0", :result {:http-code 200, :detail "Email sent"}})}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment