Git Product home page Git Product logo

component's Introduction

Component

'Component' is a tiny Clojure framework for managing the lifecycle and dependencies of software components which have runtime state.

This is primarily a design pattern with a few helper functions. It can be seen as a style of dependency injection using immutable data structures.

See the video from Clojure/West 2014 (YouTube, 40 minutes)

Releases and Dependency Information

Leiningen dependency information:

[com.stuartsierra/component "1.1.0"]

deps.edn dependency information:

com.stuartsierra/component {:mvn/version "1.1.0"}

Maven dependency information:

<dependency>
  <groupId>com.stuartsierra</groupId>
  <artifactId>component</artifactId>
  <version>1.1.0</version>
</dependency>

Dependencies and Compatibility

Starting with version 0.3.0 of 'Component', Clojure or ClojureScript version 1.7.0 or higher is required for Conditional Read support.

Version 0.2.3 of 'Component' is compatible with Clojure versions 1.4.0 and higher.

'Component' requires my dependency library

Discussion

Please post questions on the Clojure Mailing List

Introduction

For the purposes of this framework, a component is a collection of functions or procedures which share some runtime state.

Some examples of components:

  • Database access: query and insert functions sharing a database connection

  • External API service: functions to send and retrieve data sharing an HTTP connection pool

  • Web server: functions to handle different routes sharing all the runtime state of the web application, such as a session store

  • In-memory cache: functions to get and set data in a shared mutable reference such as a Clojure Atom or Ref

A component is similar in spirit to the definition of an object in Object-Oriented Programming. This does not alter the primacy of pure functions and immutable data structures in Clojure as a language. Most functions are just functions, and most data are just data. Components are intended to help manage stateful resources within a functional paradigm.

Advantages of the Component Model

Large applications often consist of many stateful processes which must be started and stopped in a particular order. The component model makes those relationships explicit and declarative, instead of implicit in imperative code.

Components provide some basic guidance for structuring a Clojure application, with boundaries between different parts of a system. Components offer some encapsulation, in the sense of grouping together related entities. Each component receives references only to the things it needs, avoiding unnecessary shared state. Instead of reaching through multiple levels of nested maps, a component can have everything it needs at most one map lookup away.

Instead of having mutable state (atoms, refs, etc.) scattered throughout different namespaces, all the stateful parts of an application can be gathered together. In some cases, using components may eliminate the need for mutable references altogether, for example to store the "current" connection to a resource such as a database. At the same time, having all state reachable via a single "system" object makes it easy to reach in and inspect any part of the application from the REPL.

The component dependency model makes it easy to swap in "stub" or "mock" implementations of a component for testing purposes, without relying on time-dependent constructs, such as with-redefs or binding, which are often subject to race conditions in multi-threaded code.

Having a coherent way to set up and tear down all the state associated with an application enables rapid development cycles without restarting the JVM. It can also make unit tests faster and more independent, since the cost of creating and starting a system is low enough that every test can create a new instance of the system.

Disadvantages of the Component Model

First and foremost, this framework works best when all parts of an application follow the same pattern. It is not easy to retrofit the component model to an existing application without major refactoring.

In particular, the 'component' library assumes that all application state is passed as arguments to the functions that use it. As a result, this framework may be awkward to use with code which relies on global or singleton references.

For small applications, declaring the dependency relationships among components may actually be more work than manually starting all the components in the correct order. You can still use the 'Lifecycle' protocol without using the dependency-injection features, but the added value of 'component' in that case is small.

The "system object" produced by this framework is a large and complex map with a lot of duplication. The same component may appear in multiple places in the map. The actual memory cost of this duplication is negligible due to persistent data structures, but the system map is typically too large to inspect visually.

You must explicitly specify all the dependency relationships among components: the code cannot discover these relationships automatically.

Finally, the 'component' library forbids cyclic dependencies among components. I believe that cyclic dependencies usually indicate architectural flaws and can be eliminated by restructuring the application. In the rare case where a cyclic dependency cannot be avoided, you can use mutable references to manage it, but this is outside the scope of 'component'.

Usage

(ns com.example.your-application
  (:require [com.stuartsierra.component :as component]))

Creating Components

To create a component, define a Clojure record that implements the Lifecycle protocol.

(defrecord Database [host port connection]
  ;; Implement the Lifecycle protocol
  component/Lifecycle

  (start [component]
    (println ";; Starting database")
    ;; In the 'start' method, initialize this component
    ;; and start it running. For example, connect to a
    ;; database, create thread pools, or initialize shared
    ;; state.
    (let [conn (connect-to-database host port)]
      ;; Return an updated version of the component with
      ;; the run-time state assoc'd in.
      (assoc component :connection conn)))

  (stop [component]
    (println ";; Stopping database")
    ;; In the 'stop' method, shut down the running
    ;; component and release any external resources it has
    ;; acquired.
    (.close connection)
    ;; Return the component, optionally modified. Remember that if you
    ;; dissoc one of a record's base fields, you get a plain map.
    (assoc component :connection nil)))

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

Define the functions implementing the behavior of the component to take an instance of the component as an argument.

(defn get-user [database username]
  (execute-query (:connection database)
    "SELECT * FROM users WHERE username = ?"
    username))

(defn add-user [database username favorite-color]
  (execute-insert (:connection database)
    "INSERT INTO users (username, favorite_color)"
    username favorite-color))

Define other components in terms of the components on which they depend.

(defrecord ExampleComponent [options cache database scheduler]
  component/Lifecycle

  (start [this]
    (println ";; Starting ExampleComponent")
    ;; In the 'start' method, a component may assume that its
    ;; dependencies are available and have already been started.
    (assoc this :admin (get-user database "admin")))

  (stop [this]
    (println ";; Stopping ExampleComponent")
    ;; Likewise, in the 'stop' method, a component may assume that its
    ;; dependencies will not be stopped until AFTER it is stopped.
    this))

Do not pass component dependencies in a constructor. Systems are responsible for injecting runtime dependencies into the components they contain: see the next section.

(defn example-component [config-options]
  (map->ExampleComponent {:options config-options
                          :cache (atom {})}))

Systems

Components are composed into systems. A system is a component which knows how to start and stop other components. It is also responsible for injecting dependencies into the components which need them.

The easiest way to create a system is with the system-map function, which takes a series of key/value pairs just like the hash-map or array-map constructors. Keys in the system map are keywords. Values in the system map are instances of components, usually records or maps.

(defn example-system [config-options]
  (let [{:keys [host port]} config-options]
    (component/system-map
      :db (new-database host port)
      :scheduler (new-scheduler)
      :app (component/using
             (example-component config-options)
             {:database  :db
              :scheduler :scheduler}))))

Specify the dependency relationships among components with the using function. using takes a component and a collection of keys naming that component's dependencies.

If the component and the system use the same keys, then you can specify dependencies as a vector of keys:

    (component/system-map
      :database (new-database host port)
      :scheduler (new-scheduler)
      :app (component/using
             (example-component config-options)
             [:database :scheduler]))
             ;; Both ExampleComponent and the system have
             ;; keys :database and :scheduler

If the component and the system use different keys, then specify them as a map of {:component-key :system-key}. That is, the using keys match the keys in the component, the values match keys in the system.

    (component/system-map
      :db (new-database host port)
      :sched (new-scheduler)
      :app (component/using
             (example-component config-options)
             {:database  :db
              :scheduler :sched}))
        ;;     ^          ^
        ;;     |          |
        ;;     |          \- Keys in the system map
        ;;     |
        ;;     \- Keys in the ExampleComponent record

The system map provides its own implementation of the Lifecycle protocol which uses this dependency information (stored as metadata on each component) to start the components in the correct order.

Before starting each component, the system will assoc its dependencies based on the metadata provided by using.

Again using the example above, the ExampleComponent would be started as if you did this:

(-> example-component
    (assoc :database (:db system))
    (assoc :scheduler (:sched system))
    (start))

Stop a system by calling the stop method on it. This will stop each component, in reverse dependency order, and then re-assoc the dependencies of each component. Note: stop is not the exact inverse of start; component dependencies will still be associated.

It doesn't matter when you associate dependency metadata on a component, as long as it happens before you call start. If you know the names of all the components in your system in advance, you could choose to add the metadata in the component's constructor:

(defrecord AnotherComponent [component-a component-b])

(defrecord AnotherSystem [component-a component-b component-c])

(defn another-component []   ; constructor
  (component/using
    (map->AnotherComponent {})
    [:component-a :component-b]))

Alternately, component dependencies can be specified all at once for all components in the system with system-using, which takes a map from component names to their dependencies.

(defn example-system [config-options]
  (let [{:keys [host port]} config-options]
    (-> (component/system-map
          :config-options config-options
          :db (new-database host port)
          :sched (new-scheduler)
          :app (example-component config-options))
        (component/system-using
          {:app {:database  :db
                 :scheduler :sched}}))))

Entry Points in Production

The 'component' library does not dictate how you store the system map or use the components it contains. That's up to you.

The typical approach differs in development and production:

In production, the system map is ephemeral. It is used to start all the components running, then it is discarded.

When your application starts, for example in a main function, construct an instance of the system and call component/start on it. Then hand off control to one or more components that represent the "entry points" of your application.

For example, you might have a web server component that starts listening for HTTP requests, or an event loop component that waits for input. Each of these components can create one or more threads in its Lifecycle start method. Then main could be as trivial as:

(defn main [] (component/start (new-system)))

Note: You will still need to keep the main thread of your application running to prevent the JVM from shutting down. One way is to block the main thread waiting for some signal to shut down; another way is to Thread/join the main thread to one of your components' threads.

This also works well in conjunction with command-line drivers such as Apache Commons Daemon.

Entry Points for Development

In development, it is useful to have a reference to the system map to examine it from the REPL.

The easiest way to do this is to def a Var to hold the system map in a development namespace. Use alter-var-root to start and stop it.

Example REPL session:

(def system (example-system {:host "dbhost.com" :port 123}))
;;=> #'examples/system

(alter-var-root #'system component/start)
;; Starting database
;; Opening database connection
;; Starting scheduler
;; Starting ExampleComponent
;; execute-query
;;=> #examples.ExampleSystem{ ... }

(alter-var-root #'system component/stop)
;; Stopping ExampleComponent
;; Stopping scheduler
;; Stopping database
;; Closing database connection
;;=> #examples.ExampleSystem{ ... }

See the reloaded template for a more elaborate example.

Web Applications

Many Clojure web frameworks and tutorials are designed around an assumption that a "handler" function exists as a global defn, without any context. With this assumption, there is no easy way to use any application-level context in the handler without making it also a global def.

The 'component' approach assumes that any "handler" function receives its state/context as an argument, without depending on any global state.

To reconcile these two approaches, create the "handler" function as a closure over one or more components in a Lifecycle start method. Pass this closure to the web framework as the "handler".

Most web frameworks or libraries that have a static defroutes or similar macro will provide an equivalent non-static routes which can be used to create a closure.

It might look something like this:

(defn app-routes
  "Returns the web handler function as a closure over the
  application component."
  [app-component]
  ;; Instead of static 'defroutes':
  (web-framework/routes
   (GET "/" request (home-page app-component request))
   (POST "/foo" request (foo-page app-component request))
   (not-found "Not Found")))

(defrecord WebServer [http-server app-component]
  component/Lifecycle
  (start [this]
    (assoc this :http-server
           (web-framework/start-http-server
             (app-routes app-component))))
  (stop [this]
    (stop-http-server http-server)
    this))

(defn web-server
  "Returns a new instance of the web server component which
  creates its handler dynamically."
  []
  (component/using (map->WebServer {})
                   [:app-component]))

More Advanced Usage

Errors

While starting/stopping a system, if any component's start or stop method throws an exception, the start-system or stop-system function will catch and wrap it in an ex-info exception with the following keys in its ex-data map:

  • :system is the current system, including all the components which have already been started.

  • :component is the component which caused the exception, with its dependencies already assoc'd in.

The original exception which the component threw is available as .getCause on the exception.

The 'Component' library makes no attempt to recover from errors in a component, but you can use the :system attached to the exception to clean up any partially-constructed state.

Since component maps may be large, with a lot of repetition, you probably don't want to log or print this exception as-is. The ex-without-components helper function will remove the larger objects from an exception.

The ex-component? helper function tells you if an exception was originated or wrapped by 'Component'.

Idempotence

You may find it useful to define your start and stop methods to be idempotent, i.e., to have effect only if the component is not already started or stopped.

(defrecord IdempotentDatabaseExample [host port connection]
  component/Lifecycle
  (start [this]
    (if connection  ; already started
      this
      (assoc this :connection (connect host port))))
  (stop [this]
    (if (not connection)  ; already stopped
      this
      (do (.close connection)
          (assoc this :connection nil)))))

The 'Component' library does not require that stop/start be idempotent, but idempotence can make it easier to clean up state after an error, because you can call stop indiscriminately on everything.

In addition, you could wrap the body of stop in a try/catch that ignores all exceptions. That way, errors stopping one component will not prevent other components from shutting down cleanly.

(try (.close connection)
  (catch Throwable t
    (log/warn t "Error when stopping component")))

Stateless Components

The default implementation of Lifecycle is a no-op. If you omit the Lifecycle protocol from a component, it can still participate in the dependency injection process.

Components which do not need a lifecycle can be ordinary Clojure maps.

You cannot omit just one of the start or stop methods: any component which implements Lifecycle must supply both.

Reloading

I developed this pattern in combination with my "reloaded" workflow. For development, I might create a user namespace like this:

(ns user
  (:require [com.stuartsierra.component :as component]
            [clojure.tools.namespace.repl :refer (refresh)]
            [examples :as app]))

(def system nil)

(defn init []
  (alter-var-root #'system
    (constantly (app/example-system {:host "dbhost.com" :port 123}))))

(defn start []
  (alter-var-root #'system component/start))

(defn stop []
  (alter-var-root #'system
    (fn [s] (when s (component/stop s)))))

(defn go []
  (init)
  (start))

(defn reset []
  (stop)
  (refresh :after 'user/go))

Subsystems

What if you want to start just some of the components in your system?

Systems are not designed to be in a "partially started" state, and the consequences are confusing.

Instead, create a new system containing just the components you need. The subsystem function (added in 1.1.0) acts like Clojure's select-keys function, but for systems, and it automatically includes all transitive dependencies.

Note: Although the start-system function takes an argument naming the components to be started, this is more of an implementation detail. It does not take dependencies into account. Use subsystem and start instead.

Usage Notes

Do not pass the system around

The top-level "system" record is used only for starting and stopping other components, and for convenience during interactive development.

See "Entry Points in ..." above.

No function should take the entire system as an argument

Application functions should never receive the whole system as an argument. This is unnecessary sharing of global state.

Rather, each function should be defined in terms of at most one component.

If a function depends on several components, then it should have its own component with dependencies on the things it needs.

No component should be aware of the system which contains it

Each component receives references only to the components on which it depends.

Do not nest systems

It's technically possible to nest one system-map in another, but the effects on dependencies are subtle and confusing.

Instead, give all your components unique keys and merge them into one system.

Other kinds of components

The "application" or "business logic" may itself be represented by one or more components.

Component records may, of course, implement other protocols besides Lifecycle.

Any type of object, not just maps and records, can be a component if it has no lifecycle and no dependencies. For example, you could put a bare Atom or core.async Channel in the system map where other components can depend on it.

Test doubles

Different implementations of a component (for example, a stub version for testing) can be injected into a system with assoc before calling start.

Notes for Library Authors

'Component' is intended as a tool for applications, not reusable libraries. I would not expect a general-purpose library to impose any particular framework on the applications which use it.

That said, library authors can make it trivially easy for applications to use their libraries in combination with the 'Component' pattern by following these guidelines:

  • Never create global mutable state (for example, an Atom or Ref stored in a def).

  • Never rely on dynamic binding to convey state (for example, the "current" database connection) unless that state is necessarily confined to a single thread.

  • Never perform side effects at the top level of a source file.

  • Encapsulate all the runtime state needed by the library in a single data structure.

  • Provide functions to construct and destroy that data structure.

  • Take the encapsulated runtime state as an argument to any library functions which depend on it.

Customization

A system map is just a record that implements the Lifecycle protocol via two public functions, start-system and stop-system. These two functions are just special cases of two other functions, update-system and update-system-reverse. (Added in 0.2.0)

You could, for example, define your own lifecycle functions as new protocols. You don't even have to use protocols and records; multimethods and ordinary maps would work as well.

Both update-system and update-system-reverse take a function as an argument and call it on each component in the system. Along the way, they assoc in the updated dependencies of each component.

The update-system function iterates over the components in dependency order: a component will be called after its dependencies. The update-system-reverse function goes in reverse dependency order: a component will be called before its dependencies.

Calling update-system with the identity function is equivalent to doing just the dependency injection part of 'Component' without Lifecycle.

References / More Information

Copyright and License

The MIT License (MIT)

Copyright © 2015 Stuart Sierra

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

component's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

component's Issues

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.

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.

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

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!

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

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

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

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

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

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

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!

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.

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.

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.

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?

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

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.

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?

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.

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?

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

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

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

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)

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

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

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

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.

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.

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?

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.

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.

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.

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!

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.

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?

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?

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

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?

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.

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.

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.

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.