Integrant Overviewλ︎
Integrant manages the life-cycle of components that are composed to create the Clojure service, i.e. start, stop, restart.
Integrant uses a declarative configuration (resources/config.edn
) to define a system configuration.
Components are managed using runtime polymorphism, i.e. defmethod
, to define how each component is managed.
init-key
start a componenthalt-key!
stop a component
Integrant REPL manages components during development to restart the services, loading all code changes into the REPL (especially useful after ranaming functions and namespaces)
integrant.repl.state/config
shows the configuration used to start the service. integrant.repl.state/system
to inspect the configuration state of the running system.
Integrant and Integrant REPL can share the same system configuration file, although are otherwise separate ways of working with a system.
Integrant configurationλ︎
Define the configuration for each part of the system, such as http server (jetty, httpkit), router application (reitit, compojure, ring) and persistence storage (postgres, crux)
Use a shared resources/config.edn
file with Integrant for consistency. Or if there is significant experimentation to be done, create a dev/resources/config.edn
file
Define composite components
{:practicalli.gameboard.service/http-server
{:handler #ig/ref :practicalli.gameboard.service/router
:port 8888
:join? false}
:practicalli.gameboard.service/router
{:persistence #ig/ref :practicalli.gameboard.service/relational-store}
:practicalli.gameboard.service/relational-store
{:connection {:url "http://localhost/" :port 57207 :database "gameboard"}}}
Fully qualified keywords, e.g. domain.service.name/component, are used so that keys are unique throughout the system.
The fully qualified name is the namespace that contains the defmethod init-key
for the key. The Integrant load-namespaces
function will automatically load all namespaces that match key names
Composite componentsλ︎
Components can be composed of configuration and references to other components, creating a composite component.
For example an HTTP component may reference a request handler component and in turn the request handler may include a database connection component.
Integrant uses the #ig/ref
tag literal to define a references to anther component.
Component relationships in a Clojure web service
- relational-store defines a database connection
- an request router includes a reference to the database connection, so handlers can be passed connection details
- http-server includes a reference to the router that will assign all requests to the relevant handler functions
{:practicalli.gameboard.service/relational-store {:connection {:url "http://localhost/" :port 57207 :database "scoreboard"}} :practicalli.gameboard.service/router {:persistence #ig/ref :practicalli.gameboard.service/relational-store}} :practicalli.gameboard.service/http-server {:handler #ig/ref :practicalli.gameboard.service/router :join? false}
Aero and Integrantλ︎
Aero defines a range of tag literals that can be used in a system configuration.
Aero does not include the #ig/ref
reference so needs to be taught how to handle this tag using a defmethod
.
Define integrant ref tag for Aero reader
Now aero can parse a system configuration EDN file that contains Integrant references
Integrant System Configurationλ︎
The system configuration is a hash-map with each component defined as a top-level key and its associated values (a hash-map of key-value pairs). Component dependencies are defined by including a component key within the definition of another component.
Defining relationships between services, such as an HTTP server and a Persistent store, are achieved by passing the relevant parts of the configuration to each service. As this is information is managed at the top level of a Clojure system, it avoids unnecessary coupling between system services.
The request router is dependant on a database connection and an external data provider connection to provide information to the handlers that satisfy the requests.
Define the request router as a composite component including these dependant components via an Integrant reference, #ig/ref