beginners

Nim Sadeh 2025-05-01T20:39:15.324229Z

2025-05-01T20:41:22.593079Z

almost certainly goose requires a symbol that resolves to a global function

Nim Sadeh 2025-05-01T20:43:09.159879Z

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.

2025-05-01T20:43:10.546879Z

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

2025-05-01T20:43:51.100849Z

and the local named f is not available to goose anyway

Nim Sadeh 2025-05-01T20:44:35.103999Z

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

Nim Sadeh 2025-05-01T20:44:51.064809Z

Basically to get enqueued function to use non-serializable dependencies

2025-05-01T20:45:35.251889Z

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

2025-05-01T20:45:54.086029Z

the answer is to on the deqeueuing side of goose adda database connection

Nim Sadeh 2025-05-01T20:46:43.277539Z

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.

2025-05-01T20:52:36.733799Z

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

Nim Sadeh 2025-05-01T20:54:58.915429Z

yea I think that is the way to go somehow

Nim Sadeh 2025-05-01T20:55:09.860459Z

If I figure it out, I will publish an issue with the solution

Nim Sadeh 2025-05-01T20:55:10.859239Z

ty

2025-05-01T20:55:15.486279Z

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)

2025-05-01T20:56:03.345229Z

And encapsulation/scoping is a primary feature (in my mind at least) of component, so there is some friction there

Nim Sadeh 2025-05-01T20:56:38.137249Z

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

2025-05-01T20:59:30.997659Z

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)

Nim Sadeh 2025-05-01T21:00:09.595419Z

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

Nim Sadeh 2025-05-01T21:01:02.731919Z

Actually it breaks trying to put the results back 😞

Nim Sadeh 2025-05-01T21:29:30.672869Z

https://github.com/nilenso/goose/issues/201

Pyi Soe 2025-05-01T05:26:25.263349Z

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?

James Amberger 2025-05-02T23:54:45.109279Z

plug: http://caveman.mccue.dev

Pyi Soe 2025-05-03T02:50:05.816069Z

@james.amberger Really interesting. Thanks! I'll take a look.

p-himik 2025-05-01T07:03:43.605879Z

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.

💯 1
Pyi Soe 2025-05-01T07:13:51.703879Z

Clear now. Thanks a lot!

👍 1
seancorfield 2025-05-01T15:31:21.900869Z

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.

Pyi Soe 2025-05-01T16:42:35.566529Z

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.

p-himik 2025-05-01T17:21:36.560099Z

Oof, I really don't like that pattern. It seems to be like it's just using IoC only to completely ignore IoC.

Nim Sadeh 2025-05-01T20:42:10.583229Z

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

Nim Sadeh 2025-05-01T20:42:15.654849Z

Hope that helps

p-himik 2025-05-01T05:53:30.195329Z

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.

Pyi Soe 2025-05-01T06:38:47.799759Z

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?