Hey, it's me. I'm thinking about https://github.com/stuartsierra/component/pull/76, so you can do (with-open [system (start system)] ... in tests.
This is now available in release [com.stuartsierra/component "1.2.0"]
https://hachyderm.io/@lambdasierra/115435699727454025
Just upgraded to this at work -- it allowed me to switch a lot of code that looked like this:
(let [sys (component/start ...)]
(try
(do-something sys)
(finally
(component/stop sys))))
to just this:
(with-open [sys (component/start ...)]
(do-something sys))
Thank you!Except that now I have reflection warnings from a lot of those:
Reflection warning, ws/batch_jobs/appleappstore.clj:24:3 - reference to field close can't be resolved.
Presumably component/start can't type hint its result to be Closeable since not all startable things are Closeable.
Yeah, I tried type-hinting. You can see my experiments in the https://github.com/stuartsierra/component/commits/component-1.2.0/.
I find that with-open often requires ^Closeable type hints in practice.
My approach in next.jdbc: https://github.com/seancorfield/next-jdbc/blob/develop/src/next/jdbc/connection.clj#L278 and https://github.com/seancorfield/next-jdbc/blob/develop/src/next/jdbc/connection.clj#L318 -- nasty but it avoids type hints in user code 🙂
(esp. since next.jdbc supports Component without depending on it)
I suppose one could also do something like
(defn closeable ^Closeable [object]
(assert (instance? Closeable object))
object)
but that's basically just type hinting with extra steps 🙂Or you could replace with-open with your own macro that calls a more generic close function.
I was over-aggressive with some of my refactoring to use with-open calls and found several "systems" at work that are not actually SystemMap and therefore not Closeable so I had to back off. Having explored this in production code, I'm not sure it's really a good change since not every started Component is compatible with with-open (i.e., not Closeable) and it makes for some fairly inconsistent code.
I might well create some with-* macro to paper over the inconsistency, since I don't really want to "close" a Component, but I do want to automatically stop a Component.
Maybe a with-start macro that automatically calls both start and stop under the hood...?
with-lifecycle
Yeah, I saw your suggestion earlier and liked it but didn't want to @ you at this point, 20+ messages into the thread. But, yeah, with-lifecycle to call start and stop makes a lot of sense to me now, having tried with-open and decided I don't like it.
me sets reminder to create JIRA ticket at work tomorrow to revisit the Component 1.2.0 upgrade work I did a few days ago 🙂
A potentially interesting quirk is stop returns the stopped system, which depending, you might want in theory you could start it again, or inspect it to or something. That seems like it would be pretty niche though.
huh! nice! 😄 thanks @stuartsierra !
Have you considered adding a with-system macro to component? One of my favorite changes in component since I have been using it is the metadata protocol support for Lifecycle, allowing using plain maps as components. While the system map is not exactly the same thing, it does feel sort of like a step in the other direction to add more methods to it.
Maybe with-lifecyle
that's what this is for
I've been doing something like this in smaller apps so yes +1 to this
does this work with extend-with-metadata system maps?
If you have a SystemMap containing components that implement Lifecycle via extend-with-metadata, then yes this still works. https://github.com/stuartsierra/component/blob/87ddaafa587b657af6b1191576875a933c0d0271/src/com/stuartsierra/component.cljc#L178 is a type defined in the Component library, not usually something you would implement yourself. If you want to implement your own system map type, then you would have to define it to implement Closeable. As far as I know, you cannot do that with extend-with-metadata because Closeable is a Java interface, not a Clojure protocol.
Yes please. We roll our own thing for this in tests and I'd be happy to get rid of it.
And I'll state the obvious, just in case it's not obvious...
...calling .close on the system map would be equivalent to calling component/stop correct?
The one caveat being you won't get the updated system map back because .close is void I believe.
Curious Sean, what are the types of your non system map systems?
Some are plain records (that implement Lifecycle). Some are hash maps with start/`stop` provided as metadata. Some are data that have no start/`stop` -- which means the defaults are used that are identity.
This is most common in tests for individual "small systems".
@hiredman Like so:
(defmacro with-lifecycle
"Starts and stops a component system around the body."
[[name expr] & body]
`(let [~name (component/start ~expr)]
(try
~@body
(finally
(component/stop ~name)))))
(59 references to this at work so far)no nesting?
Dependencies? A la component/using? Doesn't require a system map -- only something that can carry metadata. system-using works with a hash map.
We have 41 instances of component/using, 16 component/system-using. 33 calls to component/system-map. 141 calls to component/start.
no I meant multiple bindings in your with-lifecycle, like with-open supports multiple bindings by recursive expansion
Ah, no, I didn't bother since I think there is only one place in our codebase where we start'n'stop more than one component for a particular piece of (test) code.
I want to try to get some combo of components + closeable maps working hehe
https://github.com/piotr-yuxuan/closeable-map this is very interesting for me but we need the tree of deps
Nice, thanks @stuartsierra :)))
it was me haha
Ohai! @d.ian.b
thanks for the attention and embracing the suggestion 😄
You can get the tree of dependencies from a system with https://github.com/stuartsierra/component/blob/535a352f94974c8baaced0bae0a51e87fcbe1e49/src/com/stuartsierra/component.cljc#L96 or from an individual component with https://github.com/stuartsierra/component/blob/535a352f94974c8baaced0bae0a51e87fcbe1e49/src/com/stuartsierra/component.cljc#L24.