Skip to main content

Adding database component

We will be using simple sqlite in memory setup for the sake of simplicity.

Our deps.edn should look like this

deps.edn
{:paths ["src"]
:mvn/repos {"github-kepler"
{:url "https://maven.pkg.github.com/kepler16/gx.cljc"}}
:deps {org.clojure/java.jdbc {:mvn/version "0.7.8"}
org.xerial/sqlite-jdbc {:mvn/version "3.23.1"}
metosin/reitit {:mvn/version "0.5.18"}
http-kit/http-kit {:mvn/version "2.3.0"}
kepler16/gx.cljc {:local/root "../../"}}
:aliases
{:main {:main-opts ["-m" "main"]}}}

Start by updating our config file

We'll add three more nodes:

  • database config
  • database instance component
  • database populate component, to pupulate data on every startup, because our database is in-memory only

New config options:

resources/config.edn
;; db config
:sqlite/db-uri {:connection-uri "jdbc:sqlite::memory:"}

;; db instance which depends on db config
:sqlite/database {:gx/component database/sqlite
:gx/props (gx/ref :sqlite/db-uri)}

;; populate component which depends on db instance
:sqlite/db-populator {:gx/component database/db-populator
:gx/props (gx/ref :sqlite/database)}

And add a new database namespace with db-related components:

src/database.clj
(ns database
(:require [clojure.java.jdbc :as jdbc]))

;; start database instance function
(defn start!
[{db-spec :props}]
;; assuming, that in real life a database connection pool
;; should be instantiated
{:connection (jdbc/get-connection db-spec)})

;; stop database instance function
(defn stop!
[{db-spec :value}]
(.close (:connection db-spec))
nil)

;; database component
(def sqlite
{:gx/start {:gx/processor start!}
:gx/stop {:gx/processor stop!}})

;; we populate our database with a sample data
(defn populate!
[{db-spec :props}]
(jdbc/execute!
db-spec "create table users (id integer, name text, last_name text)")
(jdbc/insert-multi!
db-spec :users [{:id 1 :name "John" :last_name "Doe"}
{:id 2 :name "Peter" :last_name "Parker"}
{:id 3 :name "Richard" :last_name "Walker"}
{:id 4 :name "Sergey" :last_name "Matvienko"}])
true)

;; db-populator has only :gx/start handler
;; and there is no teardown logic during stop
;; in case of :gx/stop only status will be changed to :stopped
;; and value will be left intact
(def db-populator
{:gx/start {:gx/processor populate!}})

Somehow we need to pass our database instance to a router, lets update routers's config props:

resouces/config.edn
:http/ring-router {:gx/component components/ring-router
:gx/props {:routes (gx/ref :http/routes)
;; ref to a database instance
:database (gx/ref :sqlite/database)}}

We add new middlewrare to inject database instance into request map

src/middlewares.clj
(ns middlewares)

(defn database-middleware
[handler database]
(fn [request]
;; all handlers can access db by key from req map
(handler (assoc request :db-spec database))))

Next, update our router:

src/components.clj
(ns components
(:require [reitit.ring :as reitit-ring]
[org.httpkit.server :as http-kit]
;; import our middlewares
[middlewares :as mw]))

(def ring-router
{:gx/start
{:gx/processor
;; processor now receives database in props
(fn start-router [{{:keys [routes database]} :props}]
(reitit-ring/router
routes {:data
;; inject database via middleware
{:middleware [[mw/database-middleware database]]}}))}})

and a route handler:

src/app.clj
(ns app)

(defn get-users-handler
[req]
{:status 200
:content-type "text/plain"
:body (pr-str
;; we getting db-spec key from request map
(j/query (:db-spec req) ["select * from users"]))})

Open your browser: http://localhost:8080/users to see changes.

Full source code is available on github.