integrant

dharrigan 2019-07-30T08:23:29.019100Z

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?

sandqvist 2019-07-30T08:55:59.019400Z

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.

2019-07-30T12:48:14.019600Z

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]
      ...)))

dharrigan 2019-07-30T14:07:40.021Z

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.

dharrigan 2019-07-30T14:08:02.021200Z

Does that make sense?

dharrigan 2019-07-30T14:09:14.021400Z

perhaps I can show this

dharrigan 2019-07-30T14:12:02.021600Z

https://termbin.com/t20z

sandqvist 2019-07-30T15:39:29.021800Z

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.

sandqvist 2019-07-30T15:43:01.022100Z

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.

dharrigan 2019-07-30T17:08:19.022300Z

@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.

sandqvist 2019-07-30T17:35:44.022600Z

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.

sandqvist 2019-07-30T17:37:45.022800Z

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.

sandqvist 2019-07-30T17:39:47.023100Z

The component holds a part of your program state. The functions related to the component should take the component/state as their parameter.

sandqvist 2019-07-30T17:40:50.023300Z

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.

sandqvist 2019-07-30T17:57:32.023500Z

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.

2019-07-30T17:59:59.023800Z

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?

sandqvist 2019-07-30T18:17:01.024Z

Yes, I also use the method you described. Just trying to keep it simple.

dharrigan 2019-07-30T19:24:34.024200Z

I'm not using HTTP or Ring or middleware

dharrigan 2019-07-30T19:25:25.024400Z

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.

dharrigan 2019-07-30T19:28:46.024600Z

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.

dharrigan 2019-07-30T19:28:58.024800Z

Sorry if I'm not explaining clearly.

2019-07-30T19:43:23.025Z

There are two ways of passing dependencies around. You can pass it as a function, e.g.

2019-07-30T19:43:35.025200Z

(defn clean [flyway]
  ...)

2019-07-30T19:44:40.025400Z

Or you can use higher order functions like:

(defn make-handler [flyway]
  (fn [request]
    (clean flyway)))

2019-07-30T19:45:10.025600Z

So you can pass dependencies around as arguments, or as part of a function’s lexical scope.

2019-07-30T19:52:50.025800Z

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.

sandqvist 2019-07-30T20:04:30.026Z

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.

dharrigan 2019-07-30T20:12:42.026200Z

I really appreicate your advice Sami! 🙂 I'm trying to formulate something small for @weavejester to have a look at.