Using component for dependency injection in clojure web apps
In clojure, the common dependency injection library is component. It allows naming services in your application. Then a service can declare dependencies on other services by those names. These services can also declare start
and stop
functions to properly initialize and cleanup. When the entire system is started, it will resolve those names and inject the correct dependencies where needed.
There are some good videos about component from recent conferences:
Hooking component up into a ring app
One of the issues that comes up with ring apps is the declaration of a ring handler as a static top level form. A common pattern in clojure web apps is to see
This works fine for small apps and examples. However, app
is built at compile time. There is no way to tell the ring handler to use a in-memory database or a storing email mechanism for the test environment, while using other ones for a development or production environment.
One solution is to turn app
into a function that takes the map of services. Then it could add a middleware that adds the services to the ring request. Destructuring in routes
can then pull out the services that are wanted. For example:
Now services can be added when the handler is created. For tests, a library like kerodon can be given a handler with just test services.
But how does this work with component for the main application? Since component is used to start systems it could be used to start the database, mailer, and even the web server. The web server can declare a dependency on those services and create the service map it uses to create the handler.
When component/start
is called on a system, it will run the webserver with everything hooked up correctly. This could be done in a -main
for a standalone server.