I'm still a little confused about something with integrant. Let's say I have a defmethod that initialises a datasource. Again using integrant I "inject" that reference into another defmethod (in another namespace) that uses that datasource. All good. But, then, let's say I want to invoke another, regular function at a later time using the datasource that has been "injected". Do I have to hold on to that datasource via a def
? Altenrnatively as a alter-var-root
and sent the var inside the defmethod so I can use that datasource later on?
What you want to avoid is calling a function that calls another function etc. and finally uses a global var. Inject your dependencies next to your external input. For a web backend, I usually make a middleware which assocs the datasource into the request map. I also compose the actual handler, middleware and HTTP server with integrant.
sandqvist is right, though I’d avoid associng the datasource into the request map. It’s generally better to use lexical scoping, e.g.
(defmethod ig/init-key ::blah [_ config]
(let [ds (open-conn config)]
(fn [request]
...)))
Still not getting this. For example, I'm using flyway to initialise and control a database. I inject a datasource to migrate the db. Now then, at some point in the future, I may want to call "clean" on the flyway object created in the defmethod. This means I'll need to hold on to the flyway object created within the namespace. The only thing I can think of right now is to have a (def flyway nil)
and set that def to the value created, so that in a regular function I can invoke clean
on it.
Does that make sense?
perhaps I can show this
You already return the Flyway object from you init-key implementation. That becomes the state/initialized component. Inject the initialized component to where you need it.
If this is for a test fixture where you want to drop your schemas after tests, your halt-key implementation will receive the initialized/running component.
@sandqvist sorry, I don't follow - inject where i need it? It's not for testing, but rather for running (a client could call clean at any time). How would I inject it back into the same namespace where the defmethod is defined? I hope I'm not being too much trouble, but it's a missing piece of the puzzle.
Let's say that the client activates the cleaning by calling an HTTP endpoint. You would inject the flyway component to a middleware which adds it to the ring request map. Then your endpoint handler can give it as a parameter to your cleaning function.
In general your software will have some components waiting for input (http server/ring handler). Those components must have as their dependencies all other components required to perform their actions.
The component holds a part of your program state. The functions related to the component should take the component/state as their parameter.
Note that you can nest components to build layers of abstraction. By this I mean that a component can have a reference to another component inside it.
One benefit of doing what I'm trying to explain instead of using e.g. a datasource from a global var is that you can have multiple instances of your system in a single JVM. I always run my integration tests (from a REPL in Intellij IDEA) against a separate instance built with another database instance in docker, so I can run tests and have the system running at the same time.
Ideally you shouldn’t be adding data sources to the request map, but otherwise that’s right. @dharrigan, how is your clean
function intended to be called?
Yes, I also use the method you described. Just trying to keep it simple.
I'm not using HTTP or Ring or middleware
This is something that I would do for example, in Spring. I would autowire in a dependency into the class, then that dependency (as an instance variable, or constructor variable) would then be accessible during the entire lifetime of the bean.
So, given that all I want to do is use Integrant to "wire" in a datasource into the namespace, then at some other point in time, that datasource which has been initialised and made available to that namespace can be "reused" to perform additional actions upon it (i.e., clean or provide info). I think my little paste sorta shows how I'm "wiring" in the datasource via the defmethod. The "clean" function can be invoked at another point int time, perhaps by a database trigger, or a state change in the filesystem or something.
Sorry if I'm not explaining clearly.
There are two ways of passing dependencies around. You can pass it as a function, e.g.
(defn clean [flyway]
...)
Or you can use higher order functions like:
(defn make-handler [flyway]
(fn [request]
(clean flyway)))
So you can pass dependencies around as arguments, or as part of a function’s lexical scope.
If you come up with a simple example of something you want to do, then I can show you the way it would be wired up via Integrant. Sometimes an example makes things clearer than an explanation.
I'll stop confusing things now that weavejester is helping you, but I'll tell you that I also had a Java/Spring/OOP background. Do not put dependencies into vars in namespaces, always pass them as parameters or hold them in closures created during init like weavejester describes above. One of the first things I did with Clojure was a thing that held state in a Loom graph. I put the graph into a var in my namespace and the functions modified the graph. But then I wanted two graphs, and it wasn't possible because I couldn't instantiate namespaces. The state to be modified had to be given as a parameter. But I had to have a reference to the state somewhere, right? It goes into the integrant components.
I really appreicate your advice Sami! 🙂 I'm trying to formulate something small for @weavejester to have a look at.