missionary

benny 2024-11-16T12:13:47.186619Z

Hey everyone, I suspect I'm missing something very obvious. For exploration purposes I'm trying to use missionary to manage a datascript connection as the next level from the atom watchers. I can stand it up but I don't know where to conceptually put the datascript connection. If I put that reference in a global atom I feel like I'm using a side channel around missionary. Or am I worrying too much because if one starts a resource that listens to a tcp port, one can also affect that resource from outside missionary. Is there some simple default pattern? (Differently phrased: How are missionary resources supposed to be composed and accessed, the proper way to handle state in missionary's supervision model)

benny 2024-11-16T12:27:12.122889Z

Maybe the tension in my mind is RAII lifecycle management and the "streaming" (emitting values over time) way to use missionary.

benny 2024-11-16T13:05:31.339389Z

This is where I stopped: https://gist.github.com/bennyandresen/40038a2651d6636f4921b112490b0c6a (The goal that I didn't achieve was to compose or inject so that the handler is aware of datascript as a reference) I will be AFK the rest of the day, but thanks in advance!

👀 1
Hendrik 2024-11-16T13:05:41.349889Z

For global resources, that are needed for the whole lifetime of the program, I would not overthink it and just use mount, component etc. For local resources, that are needed dynamically, you should have a look at m/observe E.g. some pseudo code:

(defn create-resource [!]
  (let [r (new-resource)]
     (! (get-state r)
     (on-change r (fn [new-state] (! new-state)))
     #(destroy r)))

(m/observe create-resource)
If you just need to keep track of some state an atom will often be sufficient with (m/watch).

Hendrik 2024-11-16T13:14:42.390879Z

Your snippet looks fine at first glance (but I have no time to test). However, as said above, I would use mount and co to manage global things. It is simple and much more repl friendly. E.g. you can keep db state, when using in-mem db and other goodies.

Andrew Wilcox 2024-11-17T03:35:58.356989Z

I do think there's nothing wrong with using imperative code, but I also like to looking for functional ways of implementing things in case it might turn out to be simpler or easier to reuse. I note that your start-db is doing two things, creating a connection and attaching a listener to the connection. I'd try separating these, so that the function which is attaching the listener and returning a flow is passed the connection instead of creating it itself. Perhaps then start-system could create the connection, and conn could become a simple variable with the connection as its value instead of being an atom. Now you would also no longer would need the :init message to tunnel the connection value through the flow.

Andrew Wilcox 2024-11-19T04:04:02.008609Z

Earlier I had said: > I'd try separating these I missed that you were calling reset! on the connection, so my suggestion wouldn't apply to your code.

leonoel 2024-11-17T10:37:04.392129Z

If you need to access a managed resource from outside of the process that is managing that resource, then using a side-channel is your only option.

leonoel 2024-11-17T10:43:07.268229Z

To avoid this problem I usually try to compose my effects such that the processes that need resource X are supervised by the process managing resource X. Example :

(defn with-conn [schema f & args]
  (m/sp (let [conn (d/create-conn schema)]
          (try (m/? (apply f conn args))
               (finally (reset! conn nil))))))

(defn start-db [conn]
  (m/observe
    (fn [!]
      (let [tx-report-key (keyword (gensym "tx-listener"))]
        (d/listen! conn tx-report-key !)
        #(d/unlisten! conn tx-report-key)))))

(comment

  (def schema {:name {:db/unique :db.unique/identity}})

  (defn app [conn]
    (m/join {}
      (m/reduce (fn [_ x] (prn x)) nil (start-db conn))
      (m/sp
        (d/transact conn [{:name "Merlin"}])
        (d/transact conn [{:name "Voldemort"}])
        (d/transact conn [{:name "Sarah"}]))))

  (def cancel ((with-conn schema app) prn prn))
  )

benny 2024-11-17T18:27:42.567029Z

Thanks everyone. I'm not yet sure of the way to go, but this feedback helps. 🙂