Git Product home page Git Product logo

component's Issues

System fails to start with clojure 1.8.0-RC4

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

wondering the reasons to choose defrecord vs reify in stuartsierra/component

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

Error or Warn when dependencies are null

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?

Log component lifecycle

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?

Wiki Suggestion: How would I then use a component function?

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

Split update-system into build-system and try-action-system

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

init.el helper fns

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)

Question: Whether it should be okay to pass system record directly to a special component

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.

Allow Sequential collections to be used in component/using

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

Keys of already started components for `::component/component-function-threw-exception`

::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.

How to inject db transaction per http request?

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?

Component includes org.clojure/clojure as a transitive dependency

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.

Multiple dependencies of same "type"

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

Clojurescript support

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?

Exceptions in component start function does not expose the original exception

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.

When call to get-dependency fails, the reported component is always ::not-found [0.3.0]

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.

Addition of started? and stopped? functions

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.

component self reflection

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

start/stop don't call a component's start/stop when there is no dependencies in the system map...

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.

Stop called twice on composed system

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.

System does not start from uberjar with clojure-1.7.0

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

with-components macro for testing

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.

Cljs source files requiring component break refresh for clj code

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.

Type changes checks to prevent dissoc accidents?

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

How to access components in production?

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!

Sub-systems and dependencies metadata issue

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?

still dependent on IoC container?

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.

with-lifecycled?

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?

dissoc injected dependencies at shutdown

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.

Init within start, entry point & env/context

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

Non-associative components?

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

constructor documentation question

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

  1. what is "map->Database"?
    Just copying and pasting that with the rest of the source would not work.
  2. it says that this constructor is optional, however just below it uses it to construct the system.
    So probably for this just an extra comment would do, once it's actually clear how to define the constructor first..

Thanks a lot for the great work anyway!

Using ECS lib for getting rid of deftypes/defrecords/defprotocols

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.

evaling (refresh :after 'user/go) in user ns at repl does not reload user, and runs old/unchanged code.

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.

Components returning nil

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!

Cursor support?

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:

  • Maintains a path into the component tree
  • Allows different update semantics to be provided, so that it can be hooked into whatever top-level state management is being used
  • Is otherwise fairly transparent to functions that work with components

Feature request: add system key metadata to components

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

Make it possible to start a subsystem?

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

Startup with asynchronous components?

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?

Cleaning up component dependencies by default

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.

Move ::dependencies meta at the system level

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.

Provide an example explaining the ?essence? of the components idea

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.