When developing code we want new or altered routes to be reflected as soon as the code for them is loaded in the REPL. When code is deployed it's subpar to compile a reitit router on every request.
ENVIRONMENT
variable to
your .env
file.For development, we will set ENVIRONMENT
to development
.
For production, production
, etc.
ENVIRONMENT=development
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=postgres
PORT=9999
root-handler
Currying is when you take a function that takes more than one argument and turn it into a series of functions that return functions which each take a single argument.
The reason we do this is so that we can either provide both the system
and request
at the same time - in which case the routes will be compiled per-request - or
call it with system
and later call the result on a request
. In that situation
the compilation will have already happened.
(ns example.routes
(:require [clojure.tools.logging :as log]
[example.cave.routes :as cave-routes]
[example.goodbye.routes :as goodbye-routes]
[example.hello.routes :as hello-routes]
[hiccup2.core :as hiccup]
[reitit.ring :as reitit-ring]))
...
(defn root-handler
([system request]
((root-handler system) request))
([system]
(let [handler (reitit-ring/ring-handler
(reitit-ring/router
(routes system))
#'not-found-handler)]
(fn [request]
(log/info (str (:request-method request) " - " (:uri request)))
(handler request)))))
start-server
to use the two argument overload in development
and the curried overload when not in development.You can validate that this works properly by changing the ENVIRONMENT
variable
and hot loading in a change to your routes. When its set to development
that should work,
when it's not you need to restart your system to see changes.
(ns example.system
(:require [example.jobs :as jobs]
[example.routes :as routes]
[next.jdbc.connection :as connection]
[proletarian.worker :as worker]
[ring.adapter.jetty :as jetty])
(:import (com.zaxxer.hikari HikariDataSource)
(io.github.cdimascio.dotenv Dotenv)
(org.eclipse.jetty.server Server)))
(set! *warn-on-reflection* true)
...
(defn start-server
[{::keys [env] :as system}]
(let [handler (if (= (Dotenv/.get env "ENVIRONMENT") "development")
(partial #'routes/root-handler system)
(routes/root-handler system))]
(jetty/run-jetty
handler
{:port (Long/parseLong (Dotenv/.get env "PORT"))
:join? false})))
...
root-handler
When something goes wrong, a named function is easier to pick out in a stack trace than
one without a name. We are going to reuse the name of root-handler
because that will point you at the right place in the code, even if it looks a little redundant.
(ns example.routes
(:require [clojure.tools.logging :as log]
[example.cave.routes :as cave-routes]
[example.goodbye.routes :as goodbye-routes]
[example.hello.routes :as hello-routes]
[example.system :as-alias system]
[hiccup2.core :as hiccup]
[reitit.ring :as reitit-ring]))
...
(defn root-handler
([system request]
((root-handler system) request))
([system]
(let [handler (reitit-ring/ring-handler
(reitit-ring/router
(routes system))
#'not-found-handler)]
(fn root-handler [request]
(log/info (str (:request-method request) " - " (:uri request)))
(handler request)))))