stuartsierra / component Goto Github PK
View Code? Open in Web Editor NEWManaged lifecycle of stateful objects in Clojure
License: MIT License
Managed lifecycle of stateful objects in Clojure
License: MIT License
I tried to run my system with latest clojure 1.8.0-RC4 and got this error:
Exception in thread "main" java.lang.ClassCastException: clojure.lang.PersistentVector cannot be cast to clojure.lang.IMapEntry, compiling:(/tmp/form-init4056328453855830101.clj:1:73)
at clojure.lang.Compiler.load(Compiler.java:7391)
at clojure.lang.Compiler.loadFile(Compiler.java:7317)
at clojure.main$load_script.invokeStatic(main.clj:275)
at clojure.main$init_opt.invokeStatic(main.clj:277)
at clojure.main$init_opt.invoke(main.clj:277)
at clojure.main$initialize.invokeStatic(main.clj:308)
at clojure.main$null_opt.invokeStatic(main.clj:342)
at clojure.main$null_opt.invoke(main.clj:339)
at clojure.main$main.invokeStatic(main.clj:421)
at clojure.main$main.doInvoke(main.clj:384)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
Caused by: java.lang.ClassCastException: clojure.lang.PersistentVector cannot be cast to clojure.lang.IMapEntry
at com.stuartsierra.component.SystemMap.entryAt(component.clj:174)
at clojure.lang.RT.find(RT.java:821)
at clojure.core$select_keys.invokeStatic(core.clj:1497)
at clojure.core$select_keys.invoke(core.clj:1490)
at com.stuartsierra.component$dependency_graph.invokeStatic(component.clj:104)
at com.stuartsierra.component$dependency_graph.invoke(component.clj)
at com.stuartsierra.component$update_system.invokeStatic(component.clj:133)
at com.stuartsierra.component$update_system.doInvoke(component.clj)
at clojure.lang.RestFn.invoke(RestFn.java:445)
at com.stuartsierra.component$start_system.invokeStatic(component.clj:162)
at com.stuartsierra.component$start_system.invoke(component.clj)
at com.stuartsierra.component$start_system.invokeStatic(component.clj:160)
at com.stuartsierra.component$start_system.invoke(component.clj)
at com.stuartsierra.component.SystemMap.start(component.clj:177)
at funnel.bin.service$_main.invokeStatic(service.clj:97)
at funnel.bin.service$_main.doInvoke(service.clj)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.lang.Var.invoke(Var.java:379)
at user$eval2574.invokeStatic(form-init4056328453855830101.clj:1)
at user$eval2574.invoke(form-init4056328453855830101.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6927)
at clojure.lang.Compiler.eval(Compiler.java:6917)
at clojure.lang.Compiler.load(Compiler.java:7379)
... 14 more
Running with clojure 1.8.0-RC3 and lower has no problems. I tried this with component 0.3.1 and 0.2.3
Hi guys,
I'm just wondering the pros/contras that justify to choose defrecord vs reify as component fn constructor.
in the component README we can read
"To create a component, define a Clojure record that implements the Lifecycle protocol."
Yes I know that "defrecord creates an immutable persistent map which implements a protocol." but I think that the same thing can be achieved with reify (BTW: "om" way to define component) over a persistent map...
Do you think there are more reasons to set defrecord as default base fn for components?
Thanks in advance
Juan
When dependencies are not available for a component, it will usually end in tears rather quickly. Can component detect these rather than having the failure occur downstream?
Hi Stuart,
I wonder if you have any thoughts on this…
We have quite a few components in our app, and sometimes one of the components will fail to start.
It would be nice if we got log output (we use Timbre) along the lines of:
Starting Database…
Starting Cache…
Starting Web…
…when we start up the system.
Of course we can log in the (start)
function of each component but I'd like to DRY things up a little and move the logging up a level.
The way I see it we have three options. The first would be to handle this in our codebase by writing our own (start-system)
function that allows us to call something other than start
, say something like:
(defn noisy-start [component args]
(logger/info "Starting" component)
(apply start component args))
(update-system my-system [:database :cache :web :foo] noisy-start)
The second would be to write a macro along the lines of (defcomponent)
that adds the logging logic to the resulting (start)
function.
The third is to add support for logging to Component itself. Is this something you've thought about?
Given a system map
(defn get-user [database username]
(execute-query (:connection database)
"SELECT * FROM users WHERE username = ?"
username))
(def system (new-system {:bunch "of" :config "stuff}))
(alter-var-root #'system component/start)
;;is this how its then used later?
(get-user (:database system) "stuartsierra")
;;Ideally you're only digging into the system 1 level deep
Hi Stuart,
I was wondering if it makes sense to you splitting this update-system behaviour so we could have build-system fn that only assoc-dependencies and try-action fn that try-action
I'm trying to intercept system components and this update-system function is a really security box to try that :) I'd need to have the system build, then intercept selected components and after that try start fn on system
Thanks in advance
Juan
Thanks for Component!
I noticed on your Workflow Reloaded blog post that your helper fns no longer work, and since they're immensely useful, I thought I'd contribute these:
Thanks!
;; emacs, init.el
;; find all buffers names which match `reg`, regex
(defun find-buffer-regex (reg)
(interactive)
(remove-if-not #'(lambda (x) (string-match reg x))
(mapcar #'buffer-name (buffer-list))))
(defun cider-execute (command)
(interactive)
(set-buffer (car (find-buffer-regex "cider-repl.*")))
(goto-char (point-max))
(insert command)
(cider-repl-return))
(defun nrepl-reset ()
(interactive)
(cider-execute "(reset)"))
(define-key cider-mode-map (kbd "C-c r") 'nrepl-reset)
(defun nrepl-test ()
(interactive)
(cider-execute "(test)"))
(define-key cider-mode-map (kbd "C-c t") 'nrepl-test)
Quoth the readme:
The "application" or "business logic" may itself be represented by one or more components.
My web application's "business logic" is represented by a single component. I also have a component for the web router, and about 10 other components representing external services (email service, database, etc).
Because of this, the "business logic" component needs every single other component (except the router component) in the system in order to do its job.
The simplest way to give these components to my "business logic" component is to give the system record directly to the "business logic" component, rather than superfluously handing over every single other component in the system (aside from itself and the router component) when defining and initializing the "business logic" component.
Yet, your readme says:
The top-level "system" record is intended to be used exclusively for starting and stopping other components. No component in the system should depend directly on its parent system.
I do not intend that application functions should receive the top-level system as an argument. Rather, functions are defined in terms of components. Each component receives references only to the components on which it depends.
What do you suggest be done in this case, if not to give it the system record directly? Should they each be given to my "business logic" component independently? That seems excessive, so I'm wondering if you have a better idea in mind, based on the phrasing of your readme.
Hi Stuart,
When building the list of dependencies for using
dynamically we would often end up with a Sequential of some kind, right now there is a vector?
check that will force the user to call vec/into [] prior to passing the dependencies to the function. I can't seem to find a reason not to allow other Sequentials.
You have a PR here with the necessary change to make it work: #27
::component/component-function-threw-exception
error is useful in that it provides the partially started system, the failed component and its key as well. It would be useful to also have a list of keys of components that have already been started.
My usecase is to stop all those components again before doing a fix and retrying.
Without knowing which components have been started already, the only workaround I found is to calculate the start order like component does internally and take-while
until the key has been reached.
Example.
I have several db gateway components that depends from db connection component.
I have use case component that depends from gateway.
I have controller(http handler) that depends from use case.
"Depend" is a component runtime dependency, not namespace dependency.
I want to pass into gateway db transaction instead of db connect.
Db transaction will be created from original db connect per http request .
Is it possible?
To use a different version of Clojure in a project that uses component, one has to exclude org.clojure/clojure
via :exclusions
as a workaround to avoid bringing in Clojure 1.9.0 as a transitive dependency.
The fix for this is to specify :scope "provided"
on the org.clojure/clojure
dependency vector in project.clj.
I have a fix for this here -- happy to open a PR if you'd like.
I'm just getting started with component, and have reached an impasse where I cannot see a way of getting the reloading benefits of lein-ring
without introducing global state.
I've created a minimal re-production of where I have got to and was hoping to get some advise on whether what I'm aiming for is possible.
https://github.com/codeasone/component-reload-issue
I've also reached out to the lein-ring
project: weavejester/lein-ring#189
Hi! Is there a way to define multiple components that implement some kind of "interface" and then be able to query a system for all the components that meet those criteria? I want to find all the matching components without knowing the list in advance. In my case, I have a bunch of components that can handle messages of different types. I want to have one component that listens for the messages then routes them to the correct handler.
I wrote a sample below that appears to be a working solution, but seems kind of yucky with the atom
. Ideas?
(ns teller.component-test
(:require [clojure.test :refer [deftest is]]
[com.stuartsierra.component :as component]))
(defrecord Accumulator []
component/Lifecycle
(start [this]
(assoc this :atom (atom {})))
(stop [this]
(assoc this :atom nil)))
(defrecord Handler [text accumulator]
component/Lifecycle
(start [this]
(let [lookup (:atom accumulator)]
(swap! lookup assoc (-> (str "handler-" text) keyword) text)
this))
(stop [this]
(update-in this [:handlers] dissoc :lookup)))
(defrecord Handlers [accumulator]
component/Lifecycle
(start [this]
(let [lookup @(:atom accumulator)]
(assoc this :lookup lookup)))
(stop [this]
(assoc this :lookup nil)))
(defrecord Mapper [handlers]
component/Lifecycle
(start [this]
(println handlers)
this)
(stop [this]
this))
(defn build-system []
(component/system-map
:accumulator (map->Accumulator {})
:handler-a (component/using (map->Handler {:text "a"})
[:accumulator])
:handler-b (component/using (map->Handler {:text "b"})
[:accumulator])
:handlers (component/using (map->Handlers {})
[:accumulator])
:mapper (component/using (map->Mapper {})
[:handlers])))
(defn start []
(component/start (build-system)))
(deftest should-start
(let [system (start)]
(is (= {:handler-a "a"
:handler-b "b"}
(get-in system [:handlers :lookup])))))
Is there any interest in supporting clojurescript down the line? I know we are in alpha mode. Still, this would be very useful for interactive development on the client side.
The only current incompatibilities as far as I can tell are the java.lang.Object
no-op implementation and the use of Long/MAX_VALUE
in com.stuartsierra/dependency
.
If there is interest, what is your preferred method of separating out the portable code from the non-portable code and packaging releases?
It would be greate to reveal the original exception instead of the current ExceptionsInfo.
So something like this:
clojure.lang.ExceptionInfo: Error in component :web in system com.stuartsierra.component.SystemMap calling #'com.stuartsierra.component/start {
:reason :com.stuartsierra.component/component-function-threw-exception,
:function #'com.stuartsierra.component/start,
:system-key :web,
:component #xxx.web.core.Webserver{<...>}, :system #<SystemMap>}, compiling:(xxx.clj:21:24)
Should include the original exceptions like this:
clojure.lang.ExceptionInfo: Error in component :web in system com.stuartsierra.component.SystemMap calling #'com.stuartsierra.component/start {
:reason :com.stuartsierra.component/component-function-threw-exception,
:function #'com.stuartsierra.component/start,
:system-key :web,
:component #xxx.web.core.Webserver{<...>}, :system #<SystemMap>}, compiling:(xxx.clj:21:24)
:original-exception #<ArityException clojure.lang.ArityException: Wrong number of args (1) passed to: core/start>
Because without it, the ExceptionInfo gives no clue to resolve the problem.
clojure.lang.ExceptionInfo: Missing dependency :redis-customer-db of clojure.lang.Keyword expected in system at :redis-customer-db
component: :com.stuartsierra.component/not-found
dependency-key: :redis-customer-db
reason: :com.stuartsierra.component/missing-dependency
system: {...}
system-key: #object[clojure.core$key 0x4c075147 "clojure.core$key@4c075147"]
=> nil
This would be more useful if the component for whom the dependency is being resolved was reported; this is not the case:
(defn- get-dependency [system system-key component dependency-key]
(let [component (get system system-key ::not-found)]
(when (nil? component)
(throw (nil-component system system-key)))
(when (= ::not-found component)
(throw (ex-info (str "Missing dependency " dependency-key
" of " (platform/type-name component)
" expected in system at " system-key)
{:reason ::missing-dependency
:system-key key
:dependency-key dependency-key
:component component
:system system})))
component))
I believe the intent was to build the ex-info data and message around the component which was to receive the dependency, but the (let [component ...
) masks the component
parameter. Refactoring the local 'component' symbol to dependency
would help.
I'll put together a PR shortly.
The new changes in 0.4.0
break component with clojurescript compilers (both shadow-cljs and clojurescript). It seems the cljs macro hasn't caught up yet.
Since it's the only change in 0.4.0
, it's not hard to use 0.3.2
instead.
I'm just wondering about the rational of leaving out these functions in the component protocols. They are pretty useful and its nice to know whether something is running or not.
it would be nice if when running something over a system via update-system, a component could know its "name" in the system.
I am working on some code that uses component internally to organize things, and it operates off a distributed job queue type thing. the first step when it pulls a job is to sort of route it back in to the nice component world, because each job tells you the "name" of the component it is destined for. I have some generic behavior I would like to capture in a component (and of course have component wire in different dependencies to specify the behavior for different cases), when this behavior includes the component publishing work that should be routed to itself, this is challenging because the component doesn't know what name that specific instance of the generic component will have in the system.
it seems like a dynamic binding of the key in update-system would work well
I understand some of what is happening here, but was a bit thrown off by it. I was attempting to build a system map up piece by piece, and expected to see my component's start and stop methods to be called, but they weren't. I believe it's because I don't currently have any dependencies between the keys, but it seems like an oversight. Some of the components do need to create something on start, but that can't happen if start isn't called.
Here's a small example of the problem:
(require '[com.stuartsierra.component :as component])
(defrecord Queue [queue]
component/Lifecycle
(start [component]
(println "Starting queue...")
(assoc component :queue (clojure.lang.PersistentQueue/EMPTY)))
(stop [component]
(println "Stopping queue...")
(assoc component :queue nil)))
(defn new-queue []
(map->Queue {}))
(defn example-system []
(component/system-map
:queue (new-queue)))
;; This fails to call Queue's start method.
(component/start (example-system))
Unfortunately, I'm not sure what the best way of solving this problem is (I haven't dug deep enough into the code to really grasp what needs to be done to fix it). But I figured it was worth reporting.
Pardon the noobness, I'm sure the issue is 100% my bad but I just can't find the solution.
I have the following composed system defined:
(component/system-map
:core-system
(component/system-map
:a (new-a))
:app
(component/using
(component/system-map
:b (new-b)
[:core-system])))
I start it with:
started-system (component/start-system composed)
and stop it with
(component/stop-system started-system)
At stop time, I notice that :a
is stopped twice. I've made its stop
function idempotent but it still runs twice, as if its state hasn't been nullified on first run.
Any hint for solving this issue would be very appreciated.
I have a problem starting my components (a whole system actually) aot-ed and packed to uberjar with clojure 1.7.0 (beta3 and latest RC1). Switching clojure back to 1.6.0 and repackaging uberjar again solves the problem, so I guess there might be something wrong with component library itself :)
I've been using this macro to make it easier to write unit tests for a component, loosely based on with-open
:
(defmacro with-components
"Evaluates body in a try expression with names bound to the started
versions of the components passed in and a finally clause that stops
the components in reverse order."
[bindings & body]
{:pre [(vector? bindings)
(even? (count bindings))
(every? symbol? (take-nth 2 bindings))]}
(if (= (count bindings) 0)
`(do ~@body)
`(let [~(bindings 0) (start ~(bindings 1))]
(try
(with-components ~(subvec bindings 2) ~@body)
(finally (stop ~(bindings 0)))))))
Usage:
(deftest component-test
(is (= 42
(with-components [inst (component)]
(meaning-of-life inst)))
"The meaning of life of a started component should be 42."))
It feels weird to put a utility macro in this library, especially since the macro is only useful for writing tests. However, it is useful, so I figured I'd check in case it's something that you feel belongs here or elsewhere.
If you call (clojure.tools.namespace.repl/refresh)
in a project repl which contains clj and cljs source that both require com.stuartsierra.component
, the function call is reported as successful, but it doesn't seem as if any namespaces are actually reloaded after they're unloaded.
The quickest way to reproduce the problem is using the duct template:
$ lein new duct tester +cljs +site
$ cd tester
$ lein setup
Then put the following in src/cljs/tester/core.cljs
(ns tester.core
(:require [com.stuartsierra.component :as component]))
Then, back at the shell:
$ lein repl
user=> (go)
Visiting localhost:3000 gives a welcome page, but after calling (do (stop) (refresh) (start))
at the repl, no connections to the web server can be established even though all the functions return either :ok
or :started
with no error.
If you kill the repl, run lein clean
, and modify the above clojurescript source so that the component library isn't required, everything works as expected.
It looks like the problem comes from unloading and reloading the component library while there is compiled clojurescript code that uses component on the classpath. If I modify clojure.tools.namespace.reload/track-reload-one
to skip both unloading and reloading the component namespace during refresh calls, everything works fine then.
Hi Stuart,
Thanks for this library, the concept and the library itself have been of great help to us.
As mentioned by README dissoc
shouldn't be used on component records as it will change the record into a map. It is easy to forget to tell this to new component users or just to forget it in general though. This results in very unexpected results sometimes. As far as I know a component should never "change" types during a start/stop cycle. Do you think it would make sense to add assertions for unexpected type changes after stop and start? This would catch the dissoc
accidents I believe.
Cheers,
Jeroen
The README describes functions taking a component as argument:
Define the functions implementing the behavior of the component to take the component itself as an argument.
(defn get-user [database username] (execute-query (:connection database) "SELECT * FROM users WHERE username = ?" username))
I'm wondering how to pass the component/system to those functions in production mode as described also in README:
In the deployed or production version of my application, I typically have a "main" function that creates and starts the top-level system.
(ns com.example.application.main (:gen-class) (:require [com.stuartsierra.component :as component] [examples :as app])) (defn -main [& args] (let [[host port] args] (component/start (app/example-system {:host host :port port}))))
For example, how to access the components/system in Compojure route functions? Do I need to store the system to a var in production mode too (and share with development mode)?
Thanks!
If one wants to use a system as a component (not a common use case asfaik?) it is made difficult by the fact that components injected into the sub-system retain their metadata and therefore their dependencies (despite the fact the component will have already started with its dependencies assoc'd) so that when the sub-system is started, it fails as it thinks it needs to resolve the dependencies of the injected component. Would an easy fix be to move ::dependencies to a different key when a component is assoc'd into another?
I am happy to submit a PR for this if they are being accepted?
This might not be an issue, but it's something I wanted to point out from an implementer's point of view. I'm relatively new to clj, so I might be totally off the mark.
A few DI systems I've used allow total dependency swapping, including injecting a different implementation of their own self. In a typical boot process I might define a main
file that goes something like this...
di-system := initializeDiSystem()
features := ['module-a', 'module-b']
features.forEach( (feature) { feature.init(di-system) }
// module-a
component := ...
expose init( di-system ) {
di-system.registerComponent( component )
}
In this weird psuedo-language, the point I'm trying to make is that each module receives "some form" of a DI system, it doesn't really care which, and each module is responsible for registering itself. Is this possible with clj and stuartsierra/component
?'
I'm trying to get away from each and every module looking like:
(ns app-reframe.core
(:require [com.stuartsierra.component :as component]))
(defrecord SomeComp [args]
component/Lifecycle
(start [component]
(println ";; starting")
component)
(stop [component]
(println ";; stopping")
component))
Which has a hard dependency on component
, and to move to something like:
(defn init [di]
(map->SomeComp {:di di }))
But this doesn't really seem possible given the way records must be declared at top-level and not inside a function. Also, if a record implements a lifecycle protocol I don't see a way around the top-level :require
.
I'm hoping someone with more experience might be able to answer this with definitiveness one way or the other. A simple "you can't do that" would suffice.
I'm about to write a macro as a test helper that's analogous to with-open
but for Lifecycle
objects. It'll start each component, bind the started component to the given name, and stop them each in a finally
.
Is there any interest in having this in the main library?
Given that the system will "inject" dependencies into a component map (via assoc
), I believe it should, at system shutdown, dissoc
those same keys.
In my particular situation, I had a component that was a wrapper around a C3P0 connection pool. As part of stop
, the connection pool was itself .close
-d.
However, the component had been propagated to another component as a dependency.
After the successful shutdown, the SystemMap is printed to the console (in the REPL). The migrations component still included the injected key to the connection pool manager, and so the C3P0 connection pool received toString()
, which (and this is a separate comment on the quality of C3P0) caused it to spin its wheels for 30 seconds and eventually report an OutOfMemoryException wrapped in an InvocationTargetException.
I've modified my code to dissoc
any dependencies injected by the system, but I strongly feel that this needs to be either strongly noted in the documentation OR automatically performed by stop
. I'd prefer the latter.
And I'd be happy to submit a patch, but I know how you feel about them.
Hi,
entry point
My system uses an entry point component. I use that to determine and refer to the component the system-starter (ie me mostly) has started the system up from. So, perhaps today it's the webserver (lein ring server), perhaps tomorrow it's the repl/console (lein repl). The entry point matters because when I start the system from webserver, I don't want to start the webserver component. However... I still may need the webserver component there, because there is a webserver component.
init vs start
So, I was wondering... would you consider splitting the system slightly so that there are init and start functions on the protocol? Start should include a call to the (hopefully idempotent) init function. I ask, because in the start-from-webserver context above, I'd like the WS loaded, and in the dependency hierarchy, becuse it may be needed by the other parts of the system (for its state; to find the port, for example), but not like to call start on it, because it's already started... (perhaps we need to be able to tell components which one was the entry point if necessary).
Another context this separation is useful in, is testing different points than the entry points... so I might only want to start up the lower part of my system (just the DB, for example) so I can run a few things on it.
We may also need an init-inverse "destroy" method which is currently kind of conceptually part of the stop method at this point.
env
The other thing, which is fine as it is but could potentially be better, is context. If I'm in a dev context, I want certain components running, but if I'm in a test context, I want other components running. The dependency hierarchy stays the same between these, though. This concern might be overkill for your "just enough structure" constraint. I'm not sure.
What I'd like to be able to do, though, is to have several named envs that I can start up. The obvious two for web dev would be "production" and "dev". The test context would most likely be a bit different because there you want to be able to call a constructor with the set of systems you want to start (see above... where I only really want to start up a chunk of the system, or I might want to mock out certain parts of the hexagonal structure).
thanks!
I've been using component for a week or so, and I gotta say I LOVE it. It's such a nice way to build apps. Proper separation of concerns, and for one of the first times in my 20+ year dev career, I feel like I'm in full control of where I put things, and it's really nice. So, a hearty thankyou is in order. <3
One of Integrant's rationale points is that "In Component, only records or maps may have dependencies".
Since 0.4.0, Component has allowed lifecycle methods to be attached to anything that can carry metadata. That means you can attach start
/stop
to a lot more things than just records and hash maps -- in next.jdbc
's Component-compatible connection pool code, that allows a function (that returns a connection pool) to have a stop
lifecycle method that can shut down the connection pool.
The dependency information has always been metadata, but there's an inherent assumption that systems and components are associative and various logic in Component just calls get
and assoc
on components. Hence Integrant's point above still stands, even though "anything" can have a lifecycle in Component.
Would you be interested in a Pull Request that modified Component so that non-associative components could carry dependencies in their metadata (leaving the behavior unchanged for associative components)?
I'm going through the README to try to learn how to use this library and I have a couple of issues..
In the docs it's written:
Optionally, provide a constructor function that takes arguments for the essential configuration parameters of the component, leaving the runtime state blank.
(defn new-database [host port](map->Database {:host host :port port}))
But
Thanks a lot for the great work anyway!
I have recently developed an entity component system for a game I am developing in clojure. (https://github.com/damn/x.x)
I found out that component
could be simplified with my library so that all the components could be plain maps and not have to be wrapped in deftypes/defrecords.
I started creating a diff of those parts of your component library but then thought to ask your opinion first before creating a PR.
The difference is to see components as [k v]
and not a special type. Systems are just multimethods dispatching on k
and by default returning v
.
I believe this makes a more functional way of doing things, without special objects.
Hi,
As far as I can tell I'm folliwing the component reloading setup described (https://github.com/stuartsierra/component#reloading).
I make changes to application/component code, save the files, then in the user ns i eval (reset) which does the stop & refresh, which shows the files I changed reloaded (all are reloaded, 'user' ns is not), and then calls 'user/go which creates a system object and starts it.
The problem is that it is started using the old version of the start code that was refered to the last time the user ns was loaded.
To fix this I have to either eval : (require 'user :reload), or touch the user.clj file to trigger this to be reloaded and pick up the new code in the other namespaces.
I cant figure out why my repl user ns does not use the new reloaded functions from my other namespaces - why the repl in user ns has old references?
I assume i've missed somethnig simple but cant see what.
Any advice appreciated.
This is probably a documentation thing (at most).
I recently restructured one of my projects from the "workflow reloaded" style, and I made a couple of breaking changes that left me baffled for quite a while.
The real problems were that one of my components' ctors was returning nil, while another was returning nil from (start).
The error message I got in both cases (after I worked my way through the system tree) was that a required component is missing. It also told me which component was the problem, and which component required it (although which was what wasn't clear for a while).
After I actually looked at the code and realized that the problem wasn't so much that they were disappearing but just that nil values weren't allowed, it was pretty easy to sort out.
There's something that was catching the exception before my actual call to reset/refresh. I don't know where to even start looking for that, but I seriously doubt it's here.
Anyway. This really seems like a very minor nit in a really great library. Thank you!
There are a few instances where I've found myself wanting something like Om's cursors for the purposes of updating a system without having to do everything at the top level, i.e the component doesn't need to know anything about the context in which it is being used.
Slightly contrived example:
(defn add-user [user-component user-id user-data]
(update! user-component assoc-in [:users user-id] user-data))
Here the update!
function does something like Om's transact!
to the whole system, and assumes that user-component is a cursor over the underlying component.
Is this something that component could reasonably support? I think cursors could be implemented in a way that:
It would be extremely useful to debug hangs when starting large systems with nested subsystems. This is quite possible to do already, but it would be nice to have it as a helper in core.
The available extension points are almost perfect to allow users to add their own observability to component life-cycle events. It's a simple matter of calling (update-system)
passing a function that wraps (component/start)
in logging statements. The only aspect missing to fully address our requirements would be a way to find the key under which the component being started is bound in the system, in order to include that information in the logging message.
Passing the key as another argument to the f
updating function would be a breaking change since it is documented to receive the args initially passed in. However this seems like a fine usage for clojure metadata.
Something along the lines of rafaeldff@2889d5d would address this issue, in my opinion (I'm not opening a pull-request now due to the contribution instructions in the repository).
Hello! I would like to be able to start a sub-system, i.e. only selected components with all their transitive dependencies.
Use case: Only start the component(s) that a test needs skipping over other, unused and expensive ones.
I expected (start-system system component-keys)
to do that but it does not (and I am sure there are good reasons for that). Would it make sense to add a version that starts not only the requested components but also their transitive dependencies (e.g. if called with {:with-deps? true}
)? Something like:
(defn- component-keys-with-dependencies [system component-keys]
(let [dependencies (:dependencies (dependency-graph system (keys system)))]
(loop [relevant-components #{}, components2add (set component-keys)]
(if-let [deps (seq (clojure.set/difference (set (mapcat dependencies components2add)) relevant-components))]
(recur (into relevant-components components2add) (set deps))
(into relevant-components components2add)))))
(defn start-system
"Recursively starts components in the system, in dependency order,
assoc'ing in their dependencies along the way. component-keys is a
collection of keys (order doesn't matter) in the system specifying
the components to start, defaults to all keys in the system."
([system]
(start-system system (keys system)))
([system component-keys]
(start-system system component-keys nil))
([system component-keys {:keys [with-deps?] :as options}]
(if with-deps?
(start-system system (component-keys-with-dependencies system component-keys) nil)
(update-system system component-keys #'start))))
(Of course, stop-system
would need to be extended accordingly.)
I'm looking to use Component with a fairly large ClojureScript app. As part of the application startup, we need to create a websocket connection to the server, then issue some queries through that websocket before we can start using the app.
The component startup lifecycle seems to be built around making blocking calls to create stateful resources like database connections. This model doesn't really work in ClojureScript where you can't block for remote calls. Would you be interested in a PR that adds an async option for creating a system?
Hello,
While using components for a project with an architecture built around service records, I ended up repeating the following pattern for each service record :
(defrecord ComponentZ [config componentX componentY]
,,,
component/Lifecycle
(start [this]
this)
(stop [this]
(assoc this
:componentX nil
:componentY nil)))
As I understand it, it seems possible to remove such ceremony by improving the default implementation of the stop
function to remove the known dependencies automatically like this:
(extend-protocol component/Lifecycle
#?(:clj java.lang.Object :cljs default)
(start [this]
this)
(stop [this]
;; Cleanup dependencies automatically.
(reduce (fn [object dependency-key]
(assoc object dependency-key nil))
this
(-> this component/dependencies keys))))
As I understand it, this implementation is safe because components are required to be associative, and that no type information is lost in the case of records.
Let me know what you think and if I am overlooking some issue. I will open a PR in case of a positive feedback.
I favour calling component/system-using
over component/using
in my system maps as I can nicely see the entire dependency tree in one data structure.
One problem of fairly complex dependency trees is that I'd like to override certain components (for dev or testing) doing an assoc original-system :key
without duplicating the dependencies declaration code.
(def system
(-> (component/system-map
:foo (Foo. nil)
:bar (Bar.))
(component/system-using
{:foo [:bar]})))
(def dev-system (assoc system :foo (DevFoo.))) ;; dependency lost
If dependencies were stored at the system level I would only need to declare them once. I realise this would break component/using
but to me the dependency tree makes more sense as a system property, rather than as a component property. Foo could be created directly passing Bar; only in a system where :bar is part of the dependency tree should Foo declare it as a dependency. Hope it makes sense.
Almost every usage of component in projects I've inherited got one important detail wrong about its usage
e.g.
Here's a trivial database component that usually gets created
(defrecord Database [host port connection]
component/Lifecycle
(start [component]
(println ";; Starting database")
(let [conn (connect-to-database host port)]
(assoc component :connection conn)))
(stop [component]
(println ";; Stopping database")
(.close connection)
(assoc component :connection nil)))
And then, the entire codebase is littered with getting the :connection
key out of the Database component
e.g.
(some-database-library/query (:connection db-comp) query)
This feels wrong. You can never mock components this way
IMO the intention of the component library was to do the following
(defrecord Database [host port connection]
component/Lifecycle
(start [component]
(println ";; Starting database")
(let [conn (connect-to-database host port)]
(assoc component :connection conn)))
(stop [component]
(println ";; Stopping database")
(.close connection)
(assoc component :connection nil))
proto/IDatabase
(query [this] ...))
Now the rest of the code does the following
(db/query db-comp)
The approach I mentioned can be an extreme one too i.e. now, I have to provide the same interface/api in proto/IDatabase
as the library/database that I'm building a component for
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.