almost certainly goose requires a symbol that resolves to a global function
It does, the problem is that my dependencies are in a Component so are not global variables. They are arguments to functions, but are not serializable.
the symbol f is going to be namespace qualified by the reader (syntax quote namespace qualifies all symbols), so it certainly doesn't match the local f` name
and the local named f is not available to goose anyway
I need to update the example. You're right about needing a global function symbol, but that is not the point. The issue is trying to come up with a way to pass the database connection (`db`) as an argument to the function
Basically to get enqueued function to use non-serializable dependencies
yeah, the answer to that is not to try and make a db connection serializable, which is what you have to do it pass it through goosw
the answer is to on the deqeueuing side of goose adda database connection
I am not sure there's facilities for that in Goose. If there are, I can't find documentation or code describing them - that is the problem I am looking for help on, exactly waht you said, inject the db on the dequeing side.
There is someway to wrap the queue consuming in middleware in goose, so add a middleware that does it (I don't actually use goose, so dunno how that all works) https://github.com/nilenso/goose/blob/main/src/goose/brokers/rmq/worker.clj#L58
yea I think that is the way to go somehow
If I figure it out, I will publish an issue with the solution
ty
In general, rpc kind of systems (queueing function + data vs data) like this are bad for encapsulation (in order to invoke f, f most be available in some global context, and must have all its dependencies available)
And encapsulation/scoping is a primary feature (in my mind at least) of component, so there is some friction there
I can see why, do you have alternatives you like for these workflows? I am using Goose for two things: • cron scheduled jobs • Async - my application responds to texts, I give the webhook a 201 then put the work on a message broker
Something more pubsub like usualy seems like a better fit, but I am not aware of something that would drop in for goose (primarily thinking about queue management features)
Here's a toy working example
(comment
(require '[gymgreet-server.database :refer [setup-db]])
(let [producer (redis/new-producer broker-config)
db (.start (setup-db))
client-opts (assoc client/default-opts :broker producer)
mw (fn [next]
(fn [opts job]
(next opts (update job :args #(drop-last (conj % db))))))
worker-opts (assoc worker/default-opts
:broker (redis/new-consumer broker-config)
:middlewares mw)]
(worker/start worker-opts)
(client/perform-async client-opts `mock-job :nil)))Actually it breaks trying to put the results back 😞
If I am using Integrant or Component, how do I pass fully initialized system to my application handlers so that, handlers can pass the system to other functions in the call chain? Or should I pass only the components which the handlers and other functions down the call chain need? Which one is better?
plug: http://caveman.mccue.dev
@james.amberger Really interesting. Thanks! I'll take a look.
Yeah, it's something you have to deliberately decide upon - how to organize things. Suppose you have a function F that, when called, does X, Y, and Z. Suppose those three are also functions. You can do it in two ways: 1. Have F call X, Y, and Z directly - now F has to receive every single argument that X, Y, and Z require 2. Provide F with X', Y', and Z' via arguments. Those three are closures of X, Y, and Z over some values that are independent from F (`ds` is the prime candidate) What to choose is up to you, and it can be somewhere in between. Both have pros and cons.
Clear now. Thanks a lot!
FWIW, a common pattern is to write a Ring middleware fn that adds the Component system to the request hash map so that it is available to all handlers. See https://github.com/seancorfield/usermanager-example/blob/develop/src/usermanager/main.clj#L82 onward for an example.
Thank you very much. Actually, I've been lately studying your example (and integrant and reitit version of it as well). But after following https://clojureverse.org/t/passing-dependencies-into-ring-handlers-on-request-map-or-partial-ed-into-the-handler/8540, just out of curiosity I'd like to try the second approach you (and others) mentioned there.
Oof, I really don't like that pattern. It seems to be like it's just using IoC only to completely ignore IoC.
I typically write ring middleware like Sean said. Here's a middleware that attaches a database instance to every request context
(defn database-middleware
[database-conn]
{:name ::database-middleware
:wrap (fn [handler]
(fn [request]
(handler (assoc request :application/database database-conn))))})
The server builds the routes and takes database as an argument. Here's the initialization function
(defn web-server
[]
(component/using
(map->Webserver {:port 3000
:app-handler #'router})
[:database :jobs-scheduler]))
Now every request handler can access a database context
(defn handle-list-calendars
"Lists all calendars accessible to the user using their session token."
[{{:keys [calendar-source-id]} :session
:application/keys [database]}])Hope that helps
You create a system not so you can pass it around manually everywhere, but so you can avoid having to manually pass around things as much as possible. If two things that can at least partially be configured in advance and one of them needs the other, you configure it all via Integrant, including the dependency.
Umm, I am afraid I am a bit lost here. Let's say I have user-ctlr/user-edit function that calls db/update-user! , shouldn't I pass ds (data source) to user-edit so that it can pass again to update-user! ? And we configure the handler in Integrant such that its dependency, ds , is available. But I thought we normally don't configure update-user! functions with integrant.
For this case, I am considering ds which other db related functions need to use. Functions might need more than one component. So in that case we'll need to pass down all the components they require, right?