Last active
January 29, 2023 07:07
-
-
Save maacl/b0795e5f3d4ab72ca8add1e2d091e0e4 to your computer and use it in GitHub Desktop.
Domain Modelling using Clojure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns pms.core | |
(:require [clojure.spec.alpha :as s] | |
[clojure.spec.gen.alpha :as gen] | |
[clojure.spec.test.alpha :as stest])) | |
(defrecord Sale [amount]) | |
(defrecord Purchase [amount]) | |
(defn non-blank-string? [x] (and (string? x) (not (clojure.string/blank? x)))) | |
(s/def :project/id pos-int?) | |
(s/def :project/name (s/and string? seq)) | |
(s/def :project/prj-list (s/and (s/coll-of ::project :gen-max 5) seq)) | |
(s/def ::project | |
(s/or :prj (s/keys :req-un [:project/id :project/name]) | |
:prj-group (s/keys :req-un [:project/name :project/prj-list]))) | |
(s/def ::money decimal?) | |
(s/def :budget/income ::money) | |
(s/def :budget/expenditure ::money) | |
(s/def ::budget (s/keys :req-un [:budget/income :budget/expenditure])) | |
(s/def ::transaction (s/or :sale #(instance? % Sale) | |
:purchase #(instance? % Purchase))) | |
(s/def :report/budget-profit ::money) | |
(s/def :report/net-profit ::money) | |
(s/def :report/difference ::money) | |
(s/def ::report (s/keys :req-un [:report/budget-profit :report/net-profit :report/budget-profit])) | |
nil [{:keys [name prj-list] {:keys [budget-profit net-profit difference]}} & [indent]] | |
(let [indent (or indent "")] | |
(str name " - " "Budg.p.: " budget-profit " Net.p.: " net-profit " Diff.: " difference | |
(defmulti pp-project (fn [p & [indent]] (:id p))) | |
(defmethod pp-project nil [{:keys [name prj-list] | |
{:keys [budget-profit net-profit difference] :as report} :report} | |
& [indent]] | |
(let [indent (or indent "")] | |
(str name " - " "Budg.p.: " budget-profit " Net.p.: " net-profit " Diff.: " difference "\n" | |
(apply str | |
(for [p (butlast prj-list)] | |
(str indent "|\n" indent "+-" | |
(pp-project p (str indent "| ")) | |
"\n"))) | |
indent "|\n" indent "`-" | |
(pp-project (last prj-list) (str indent " "))))) | |
(defmethod pp-project :default [{:keys [id name] | |
{:keys [budget-profit net-profit difference] :as report} :report} | |
& [_]] | |
(str " " name " [" id "] " "Budg.p.: " budget-profit " Net.p.: " net-profit " Diff.: " difference)) | |
(defn get-budget [_] | |
{:income (bigdec (/ (rand-int 1000000) 100)) :expenditure (bigdec (/ (rand-int 1000000) 100))}) | |
(defn get-transactions [_] | |
[(->Sale (bigdec (/ (rand-int 400000) 100))) (->Purchase (bigdec (/ (rand-int 400000) 100)))]) | |
(defprotocol Transactable | |
(transact [t])) | |
(extend-protocol Transactable | |
Sale | |
(transact [t] | |
(:amount t)) | |
Purchase | |
(transact [t] | |
(-' (:amount t)))) | |
(defn calculate-report [{:keys [income expenditure]} transactions] | |
(let [budget-profit (- income expenditure) | |
net-profit (reduce + (map transact transactions))] | |
{:budget-profit budget-profit | |
:net-profit net-profit | |
:difference (- net-profit budget-profit)})) | |
(defmulti calculate-project-report :prj-list) | |
(defmethod calculate-project-report nil [p] | |
(assoc p :report | |
(calculate-report (get-budget p) (get-transactions p)))) | |
(defmethod calculate-project-report :default [p] | |
(let [reported-prj-list (map calculate-project-report (:prj-list p))] | |
(assoc p :report | |
(reduce (partial merge-with +) (map :report reported-prj-list)) | |
:prj-list reported-prj-list))) | |
(def some-project | |
{:name "Sweden" | |
:prj-list [{:name "Stockholm" | |
:prj-list [{:id 1 :name "Djurgaarden"} | |
{:id 2 :name "Skaergaarden"}]} | |
{:id 3 | |
:name "Gothenborg"} | |
{:name "Malmo" | |
:prj-list [{:name "Malmo City" | |
:prj-list [{:id 41 :name "Fosie1"} | |
{:id 42 :name "Fosie2"} | |
{:name "Fosie3" | |
:prj-list [{:id 31 :name "Djurgaarden"} | |
{:id 32 :name "Skaergaarden"}]} | |
{:id 5 :name "Rosengaard"}]} | |
{:name "Limhamn" | |
:prj-list [{:id 6 :name "Kalkbrottet"} | |
{:id 7 :name "Sibbarp"}]}]} | |
{:id 4 | |
:name "Eskilstuna"} | |
]}) | |
(print (pp-project (calculate-project-report some-project))) | |
(print (pp-project (calculate-project-report (first (gen/sample (s/gen ::project) 1))))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defproject pms "0.1.0-SNAPSHOT" | |
:description "FIXME: write description" | |
:url "http://example.com/FIXME" | |
:license {:name "Eclipse Public License" | |
:url "http://www.eclipse.org/legal/epl-v10.html"} | |
:dependencies [[org.clojure/clojure "1.9.0"] | |
[org.clojure/spec.alpha "0.1.143"]] | |
:main ^:skip-aot pms.core | |
:target-path "target/%s" | |
:profiles {:uberjar {:aot :all} | |
:dev {:dependencies [[org.clojure/test.check "0.9.0"]]}}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looking back at this, I love how you added more projects with good names. 👍