-
Notifications
You must be signed in to change notification settings - Fork 16
Dependency Injection
When working with background jobs in Goose, a common requirement is providing stateful dependencies, such as database connections or caches, to your job functions. This document outlines the recommended and alternative patterns for dependency injection.
We recommend using a custom middleware for passing dependencies to job functions. The middleware intercepts a job before execution and appends or prepends the necessary dependency to the job function's argument list. Consider a job that requires a database connection. You can create a middleware that adds the db connection as the first argument to the job.
(defn inject-db
[db]
(fn [next]
(fn [opts job]
(next opts (update job :args #(cons db %))))))
(defn init-worker [db & other-dependencies]
(let [worker-opts (assoc w/default-opts
:broker consumer
:middleware (comp ; top to bottom in first-in-last-out order.
(some-other-middleware)
(inject-db db))]
(w/start worker-opts)))The inject-db middleware takes a db connection and returns a function that modifies the job's arguments before the next middleware or the job itself is called. It prepends the db object to the existing arguments without modifying other arguments.
When you enqueue a job, you do so without the dependency argument:
(defn my-task [db arg-1 arg-2]
(do-something db arg-1 arg-2))
(client/perform-async `my-task arg-1 arg-2)Tip
All your job functions will receive db as the first argument when using the inject-db middleware. Any job function that doesn't require the db dependency will need to ignore the first argument explicitly.
With this approach, we use global state to hold the dependencies. We do not recommend it because it creates an unpredictable state.
- It allows any part of the application to access a component's internal state, violating the system's lifecycle and boundaries.
- It makes testing complex and leads to tightly coupled code.
(def ^:dynamic *database* nil)
(defn inject-db
[db]
(fn [next]
(fn [opts job]
(binding [*database* db]
(next opts job)))))With this middleware in place, your job function (my-task) can access the database connection through the *database* var without passing it as an explicit argument. This can simplify the function signature, but relies on dynamic vars.
Another method is to set the dependency in a globally accessible var when your application components initialise. The job functions can reference that var as needed.
Home | Getting Started | RabbitMQ | Redis | Error Handling | Monitoring | Production Readiness | Troubleshooting
Need help? Open an issue or ping us on #goose @Clojurians slack.