(defn with-db-pool [db-config f] (let [db-pool (start-pool! db-config)] (try (f db-pool) (finally (stop-pool! db-pool)))))
(with-db-pool {...} (fn [db-pool] (with-web-server {:handler (make-handler {:db-pool db-pool}) :port ...} (fn [web-server] ;; TODO: Ah. We've run out of turtles. :( ))))
(:require [yoyo]) (yoyo/run-system! (fn [latch] (with-db-pool {...} (fn [db-pool] (with-web-server {:handler (make-handler {:db-pool db-pool}) ; n.b. we have access to the db-pool here - no need for global state! :port ...} (fn [web-server] (latch))))))) ; Aha!run-system! then returns a promise - deliver any value to it, and it'll stop the system.
(yoyo/run-system!(fn [latch](ylet [db-pool (with-db-pool {...}):let [server-opts {:handler (make-handler {:db-pool db-pool}):port 3000}]web-server (with-web-server server-opts)](do-this web-server)(do-that db-pool web-server)(latch))))
(yoyo/foo! [db-pool (with-db-pool {...}):let [server-opts {:handler (make-handler {:db-pool db-pool}):port 3000}]web-server (with-web-server server-opts)](do-this web-server)(do-that db-pool web-server))
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
;; (docs for ylet at https://github.com/james-henderson/yoyo#introducing-ylet )(require '[yoyo :refer [ylet]])(defn with-connections [config f](ylet [db-pool (with-db-pool (:db config))es-conn (with-es-connection (:elasticsearch config))](f {:db-pool db-pool:es-conn es-conn})))(defn make-system [latch](let [config ...](ylet [connections (with-connections system)_ (with-webserver {:handler (make-handler (merge connections{:config config})):port 3000})](latch))))
Hi Atamert - thanks :)I thought it might be preferable to keep the call to (latch)explicit - it means that ylet can be used in nested calls, too - for example, to set up and compose groups of components/sub-systems: (contrived example, though!);; (docs for ylet at https://github.com/james-henderson/yoyo#introducing-ylet )(require '[yoyo :refer [ylet]])(defn with-connections [config f](ylet [db-pool (with-db-pool (:db config))es-conn (with-es-connection (:elasticsearch config))](f {:db-pool db-pool:es-conn es-conn})))(defn make-system [latch](let [config ...](ylet [connections (with-connections system)_ (with-webserver {:handler (make-handler (merge connections{:config config})):port 3000})](latch))))How would you see the with-* functions working, btw?
On Tue, Jun 23, 2015 at 11:47 PM, James Henderson <ja...@jarohen.me.uk> wrote:Hi Atamert - thanks :)I thought it might be preferable to keep the call to (latch)explicit - it means that ylet can be used in nested calls, too - for example, to set up and compose groups of components/sub-systems: (contrived example, though!);; (docs for ylet at https://github.com/james-henderson/yoyo#introducing-ylet )(require '[yoyo :refer [ylet]])(defn with-connections [config f](ylet [db-pool (with-db-pool (:db config))es-conn (with-es-connection (:elasticsearch config))](f {:db-pool db-pool:es-conn es-conn})))(defn make-system [latch](let [config ...](ylet [connections (with-connections system)_ (with-webserver {:handler (make-handler (merge connections{:config config})):port 3000})](latch))))How would you see the with-* functions working, btw?I think the general idea should be to provide a clean API to the consumer (of your lib). Perhaps something that accepts a start function, a stop function and some sort of main loop (f in your example).
Hey,interesting approach but I don't like the nesting and "manual" wiring of dependencies.
I don't quite like that every with-* function remains on the stack as well, but it shouldn't hurt that much.
An uncaught exception will also take down your entire system, but I guess you'd have a try/catch in your "latch" anyways.
But what I miss the most is an instance of your "app" (ie. all components together). You create it yourself in the example but I really want that always. Sometimes you just want to access your system from the outside just to see whats up (eg. REPL into a live system). I also consider the webserver to be a "client" of my "app" and not part of it (or another layer of it if you will), but that is a topic for another day.
Way way back in the day I used to work with (and on) PicoContainer which was/is a dependency injection and lifecycle management container. I tried writing a DSL for it (in Groovy, this was 2003 or so) but concluded that Java already was good enough to set everything up, a DSL (or XML) is overkill. All you need to describe a "Component" is:a) what are its dependenciesb) how do I start itc) how do I stop itIn that light I wrote my own "dependency injection" helper functions since nothing like Stuart's Component existed at the time I got into Clojure. I don't like Component due to its invasive protocol but in essence I do the same.In my system I just set up a map of components and use that as a descriptor for wiring:{:a {:depends-on []:start my.components.a/start:stop my.components.a/stop}:b {:depends-on [:a]:start my.components.b/start:stop my.components.b/stop}}The key in the outer map becomes whatever the :start function returns and is refered to it by its name :a (the key of the map). The :start function of :b is called as (my.components.b/start instance-of-a). An instance of a component is treated as an opaque value and other components interact with it only via its "public" interface (ie. my.components.a). Whether this is done via a protocol or not doesn't matter. When a shutdown is requested the :stop function is called with the instance of the component as the argument.That is about it. Mocking is just assoc over default descriptor map and I have helper functions to only do partial start/stop calls if only a specific component is needed (eg. I only need :a).Like I said it basically does the same stuff as Component, just a little less invasive since I think a component should not know about the container it runs in.
Hope that was somewhat useful as feedback to Yo-Yo.
Hey James,"the webserver being a client" is really very simple. Basically instead of starting one "app" you start two. Your actual "app" and the "web-app" that depends on "app". One contains your business logic and the other everything related to translating HTTP to app API calls. "app" doesn't know about the web part.