Clojure Integrant Exercise

Image for post
Image for post
Clojure Integrant Exercise in IntelliJ IDEA / Cursive IDE.

Introduction

Why Integrant?

Integrant State Configuration as Edn

:backend/csv {:profile #ig/ref :backend/profile
:active-db #ig/ref :backend/active-db
:data-dir "dev-resources/data"}
:backend/ddb {:active-db #ig/ref :backend/active-db
:ss-table-prefix "ss"
:ss-env #profile {:prod "prod"
:dev "dev"
:test "test"}
:endpoint {:protocol :http :hostname "localhost" :port 8000}
:aws-profile "local-dynamodb"}
:backend/postgres {:active-db #ig/ref :backend/active-db
:adapter "postgresql"
:username #or [#env DB_USERNAME "simpleserver"]
:password #or [#env DB_PASSWORD "simpleserver"]
:server-name #or [#env DB_HOST "localhost"]
:port-number #long #or [#env DB_PORT 5532]
:database-name #or [#env DB_NAME "simpleserver"]
...
; Gather different data store services here.
:backend/service {:profile #ig/ref :backend/profile
:active-db #ig/ref :backend/active-db
:csv #ig/ref :backend/csv
:ddb #ig/ref :backend/ddb
:postgres #ig/ref :backend/postgres
}

Different States with Integrant

(defmethod ig/init-key :backend/csv [_ {:keys [profile active-db data-dir]}]
(log/debug "ENTER ig/init-key :backend/csv")
; We simulate this data store using atom.
; We initialize the "db" from :data-dir.
(if (= active-db :csv)
(let [csv-data
{:data-dir data-dir
:db (atom {:domain {}
:session #{}
:user {}})}]
; Let's keep the test database empty.
(if (not= profile :test)
(csv-db-loader/load-csv-db csv-data))
(:db csv-data))))
(defmethod ig/init-key :backend/ddb [_ {:keys [active-db ss-table-prefix ss-env endpoint aws-profile]}]
(log/debug "ENTER ig/init-key :backend/ddb")
(if (= active-db :ddb)
(ddb-config/get-dynamodb-config ss-table-prefix ss-env endpoint aws-profile)))
(defmethod ig/init-key :backend/postgres [_ opts]
(log/debug "ENTER ig/init-key :backend/postgres")
(if (= (:active-db opts) :postgres)
{:datasource (hikari-cp/make-datasource (dissoc opts :active-db)) :active-db (:active-db opts)}))
(defn test-config []
(let [test-port (random-port)
; Overriding the port with random port, see TODO below.
_ (log/debug (str "test-config, using web-server test port: " test-port))]
(-> (core/system-config :test)
;; Use the same data dir also for test system. It just initializes data.
(assoc-in [:backend/csv :data-dir] "dev-resources/data")
(assoc-in [:backend/jetty :port] test-port)
;; In Postgres test setup use simpleserver_test database.
(assoc-in [:backend/postgres :database-name] "simpleserver_test")
;; No nrepl needed in tests. If used, use other port than the main system.
(assoc-in [:backend/nrepl :bind] nil)
(assoc-in [:backend/nrepl :port] nil)
(assoc-in [:backend/csv :port] nil))))

Resetting the State

(user/env)
=>
{:profile :dev,
:active-db :postgres,
:service {:domain #simpleserver.service.domain.domain_postgres.PostgresR{:db {:datasource #object[com.zaxxer.hikari.HikariDataSource
0x42ab0edd
"HikariDataSource (HikariPool-33)"],
...
(simpleserver.test-config/go)
(simpleserver.test-config/test-env)
=>
{:profile :test,
:active-db :postgres,
:service {:domain #simpleserver.service.domain.domain_postgres.PostgresR{:db {:datasource #object[com.zaxxer.hikari.HikariDataSource
0x780ac829
"HikariDataSource (HikariPool-35)"],
...

Running Tests

λ> just
Available recipes:
backend # Start backend repl.
backend-kari # Start backend repl with my toolbox.
dynamodb # Start local dynamodb emulator
lint # Lint
list
postgres # Start local postgres
test db # Test
# First Postgres...
λ> just postgres
NOTE: Remember to destroy the container if running again!
Starting docker compose...
Creating ss-postgres_postgres_1 ... done
Creating Simple Server schemas...
...
# ...and then DynamoDB...
λ> just dynamodb
Sending build context to Docker daemon 62.11MB
Step 1/15 : FROM python:3.8.2-slim-buster
---> e7d894e42148
Step 2/15 : RUN rm /bin/sh && ln -s /bin/bash /bin/sh
---> Using cache
...
Successfully tagged ss-uploader:0.1
Creating ss-dynamodb_local-dynamodb_1 ... done
Creating ss-dynamodb_uploader-app_1 ... done
Attaching to ss-dynamodb_local-dynamodb_1, ss-dynamodb_uploader-app_1
...
; csv, ddb, postgres
;:backend/active-db #or [#env SS_DB :csv]
;:backend/active-db #or [#env SS_DB :ddb]
:backend/active-db #or [#env SS_DB :postgres]
...
Image for post
Image for post
Running tests in Clojure Integrant Exercise in IntelliJ IDEA / Cursive IDE.
# Test
@test db:
./run-tests.sh
...
MYDB=$1
if [[ "$MYDB" =~ ^(csv|ddb|postgres)$ ]]; then
echo "Starting tests with $MYDB configuration..."
else
echo "Unknown DB configuration: $MYDB, exiting..."
exit 2
fi
SS_DB=$MYDB clojure -A:dev:test:common:backend -m kaocha.runner
# First using DynamoDB as data store...
λ> just test ddb
Starting tests with ddb configuration...
[(........)(...........................)(...)(.......)]
14 tests, 45 assertions, 0 failures.
# ...and then Postgres:
λ> just test postgres
Starting tests with postgres configuration...
[(...)(...........................)(.......)(........)]
14 tests, 45 assertions, 0 failures.

Conclusions

I’m a Software architect and developer. Currently implementing systems on AWS / GCP / Azure / Docker / Kubernetes using Java, Python, Go and Clojure.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store