├── CONTRIBUTING.md ├── .gitignore ├── project.clj ├── LICENSE.txt ├── test └── com │ └── stuartsierra │ ├── closeable_test.clj │ └── component_test.clj ├── dev ├── user.clj └── examples.clj ├── src └── com │ └── stuartsierra │ ├── component │ ├── platform.clj │ └── platform.cljs │ └── component.cljc ├── CHANGES.md └── README.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Please do not send pull requests without prior discussion. 4 | Please contact me via email first. Thank you. 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | .nrepl-port 14 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.stuartsierra/component "1.2.1-SNAPSHOT" 2 | :description "Managed lifecycle of stateful objects" 3 | :url "https://github.com/stuartsierra/component" 4 | :license {:name "The MIT License" 5 | :url "http://opensource.org/licenses/MIT"} 6 | :dependencies [[com.stuartsierra/dependency "1.0.0"] 7 | [org.clojure/clojure "1.7.0" :scope "provided"]]) 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2013 Stuart Sierra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/com/stuartsierra/closeable_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.stuartsierra.closeable-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [com.stuartsierra.component :as component])) 4 | 5 | ;; Compiling these tests should not generate reflection warnings 6 | (set! *warn-on-reflection* true) 7 | 8 | (defrecord TestComponent [state] 9 | component/Lifecycle 10 | (start [this] 11 | (reset! state :started) 12 | this) 13 | (stop [this] 14 | (reset! state :stopped) 15 | this)) 16 | 17 | (deftest t-closeable 18 | (testing "SystemMap can be stopped via java.io.Closeable" 19 | (let [state (atom nil) 20 | test-component (->TestComponent state) 21 | system (component/system-map :test test-component)] 22 | (with-open [^java.io.Closeable system (component/start system)] 23 | (is (instance? java.io.Closeable system)) 24 | (is (= :started @state))) 25 | (is (= :stopped @state))))) 26 | 27 | (deftest t-autocloseable 28 | (testing "SystemMap can be stopped via java.lang.AutoCloseable" 29 | (let [state (atom nil) 30 | test-component (->TestComponent state) 31 | system (component/system-map :test test-component)] 32 | (with-open [^java.lang.AutoCloseable system (component/start system)] 33 | (is (instance? java.lang.AutoCloseable system)) 34 | (is (= :started @state))) 35 | (is (= :stopped @state))))) 36 | -------------------------------------------------------------------------------- /dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | "Tools for interactive development with the REPL. This file should 3 | not be included in a production build of the application." 4 | (:require 5 | [clojure.java.io :as io] 6 | [clojure.java.javadoc :refer (javadoc)] 7 | [clojure.pprint :refer (pprint)] 8 | [clojure.reflect :refer (reflect)] 9 | [clojure.repl :refer (apropos dir doc find-doc pst source)] 10 | [clojure.set :as set] 11 | [clojure.string :as str] 12 | [clojure.test :as test] 13 | [clojure.tools.namespace.repl :refer (refresh refresh-all)] 14 | [com.stuartsierra.component :refer :all])) 15 | 16 | (defrecord DummyComponent [name deps] 17 | Lifecycle 18 | (start [this] (prn 'start name) (assoc this :started true)) 19 | (stop [this] (prn 'stop name) (assoc this :started false))) 20 | 21 | (defn dummy-component [name & deps] 22 | (using (->DummyComponent name deps) 23 | (vec deps))) 24 | 25 | (def database (dummy-component :database)) 26 | (def message-broker (dummy-component :message-broker)) 27 | (def scheduler (dummy-component :scheduler :database)) 28 | (def background-job (dummy-component :background-job :scheduler :database :message-broker)) 29 | (def application (dummy-component :application :database :message-broker)) 30 | (def web-server (dummy-component :web-server :application)) 31 | 32 | (defrecord MySystem [] 33 | Lifecycle 34 | (start [this] 35 | (start-system this (keys this))) 36 | (stop [this] 37 | (stop-system this (keys this)))) 38 | 39 | (defn my-system [] 40 | (map->MySystem {:database database 41 | :message-broker message-broker 42 | :scheduler scheduler 43 | :background-job background-job 44 | :application application 45 | :web-server web-server})) 46 | -------------------------------------------------------------------------------- /src/com/stuartsierra/component/platform.clj: -------------------------------------------------------------------------------- 1 | (ns com.stuartsierra.component.platform 2 | "Platform-specific implementation details for Component on 3 | Clojure(JVM). This is not a public API.") 4 | 5 | (set! *warn-on-reflection* true) 6 | 7 | (defn argument-error [^String message] 8 | (IllegalArgumentException. message)) 9 | 10 | (defn type-name 11 | "Returns a string name for the type/class of x." 12 | [x] 13 | (let [t (type x)] 14 | (if (class? t) 15 | (.getName ^Class t) 16 | (str t)))) 17 | 18 | (defn alter-ex-data 19 | "Returns a new ExceptionInfo with the same details as throwable and 20 | ex-data as the result of (apply f (ex-data throwable) args)." 21 | [^Throwable throwable f & args] 22 | (let [^Throwable ex 23 | (ex-info (.getMessage throwable) 24 | (apply f (ex-data throwable) args) 25 | (.getCause throwable))] 26 | ;; .getStackTrace should never be null, but .setStackTrace 27 | ;; doesn't allow null, so we'll be careful 28 | (when-let [stacktrace (.getStackTrace throwable)] 29 | (.setStackTrace ex stacktrace)) 30 | ex)) 31 | 32 | ;; Copyright © 2015 Stuart Sierra 33 | 34 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy of 35 | ;; this software and associated documentation files (the "Software"), to deal in 36 | ;; the Software without restriction, including without limitation the rights to 37 | ;; use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 38 | ;; the Software, and to permit persons to whom the Software is furnished to do so, 39 | ;; subject to the following conditions: 40 | 41 | ;; The above copyright notice and this permission notice shall be included in all 42 | ;; copies or substantial portions of the Software. 43 | 44 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 46 | ;; FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 47 | ;; COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 48 | ;; IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 49 | ;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 50 | -------------------------------------------------------------------------------- /src/com/stuartsierra/component/platform.cljs: -------------------------------------------------------------------------------- 1 | (ns com.stuartsierra.component.platform 2 | "Platform-specific implementation details for Component on 3 | ClojureScript (JavaScript). This is not a public API.") 4 | 5 | (defn argument-error [message] 6 | (ex-info message {:reason ::illegal-argument})) 7 | 8 | (defn type-name 9 | "Returns a string name for the type/class of x." 10 | [x] 11 | (type->str (type x))) 12 | 13 | (defn alter-ex-data 14 | "Returns a new ExceptionInfo with the same details as error and 15 | ex-data as the result of (apply f (ex-data throwable) args)." 16 | [error f & args] 17 | (let [ex (ex-info (ex-message error) 18 | (apply f (ex-data error) args) 19 | (ex-cause error))] 20 | ;; Set same properties as cljs.core/ex-info: 21 | (set! (.-name ex) (.-name error)) 22 | (set! (.-description ex) (.-description error)) 23 | (set! (.-number ex) (.-number error)) 24 | (set! (.-fileName ex) (.-fileName error)) 25 | (set! (.-lineNumber ex) (.-lineNumber error)) 26 | (set! (.-columnNumber ex) (.-columnNumber error)) 27 | (set! (.-stack ex) (.-stack error)) 28 | ex)) 29 | 30 | ;; Copyright © 2015 Stuart Sierra 31 | 32 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy of 33 | ;; this software and associated documentation files (the "Software"), to deal in 34 | ;; the Software without restriction, including without limitation the rights to 35 | ;; use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 36 | ;; the Software, and to permit persons to whom the Software is furnished to do so, 37 | ;; subject to the following conditions: 38 | 39 | ;; The above copyright notice and this permission notice shall be included in all 40 | ;; copies or substantial portions of the Software. 41 | 42 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 44 | ;; FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 45 | ;; COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 46 | ;; IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 47 | ;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Component Change Log 2 | 3 | 4 | ## 1.2.x series 5 | 6 | ### Version [1.2.0] released on October 25, 2025 7 | 8 | * Add implementation of `java.io.Closeable` on `SystemMap`, 9 | so `.close` on a system calls `component/stop`. 10 | See [#76]. 11 | 12 | This allows use of [with-open](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/with-open) 13 | on systems, for example `(with-open [system (start system)] ...)` 14 | in tests. 15 | 16 | Fulfulls a similar purpose as the various `with-*` macros 17 | proposed in [#6], [#7], and elsewhere. 18 | Also inspired by a conversation with [Ian Fernandez](https://github.com/ianffcs) 19 | at [Clojure South 2025](https://clojure-south.com/). 20 | 21 | 22 | 23 | ## 1.1.x series 24 | 25 | ### Version [1.1.0] released on February 26, 2022 26 | 27 | * Add `subsystem` 28 | (motivated by conversations in [#69] and elsewhere) 29 | 30 | 31 | 32 | ## 1.0.x series 33 | 34 | ### Version [1.0.0] released on March 7, 2020 35 | 36 | * Modify Clojure dependency to be `:scope "provided"` 37 | to prevent spurious warnings when overridden by a 38 | later version. Note that this also prevents warnings 39 | when overridden by an **earlier, incompatible** 40 | version of Clojure. Component since version 0.3.0 41 | requires Clojure 1.7.0 or later. 42 | ([commit 5af4ad06]; reported by Dave Yarwood in [#65]). 43 | 44 | * Update 'dependency' library to version 1.0.0 45 | 46 | 47 | 48 | ## 0.4.x series 49 | 50 | ### Version [0.4.0] released on December 30, 2018 51 | 52 | * Add `:extend-via-metadata` to Lifecycle protocol 53 | for Clojure 1.10 [extend-via-metadata]. 54 | Backwards-compatible with earlier Clojure versions 55 | ([commit 7824f551]; suggested by Joe Lane in [#62]). 56 | 57 | * **Not** backwards-compatible with ClojureScript versions 58 | before [ClojureScript 1.10.516] 59 | (reported by Ryan Schmukler in [#63]). 60 | 61 | 62 | 63 | ## 0.3.x series 64 | 65 | ### Version [0.3.2] released on December 12, 2016 66 | 67 | * Fix incorrect base class for extending Lifecycle 68 | ([commit 69e62854]; reported by Tim Pote) 69 | 70 | ### Version [0.3.1] released on November 28, 2015 71 | 72 | * Fix incorrect values for `ex-data` keys in missing-dependency 73 | error (reported by Howard Lewis Ship at [#40]) 74 | 75 | ### Version [0.3.0] release on September 18, 2015 76 | 77 | * API-compatible with 0.2.x 78 | 79 | * Minimum Clojure version 1.7.0 for Conditional Read 80 | 81 | * Added ClojureScript support via Conditional Read 82 | 83 | 84 | 85 | ## 0.2.x series 86 | 87 | ### Version [0.2.3] released on March 3, 2015 88 | 89 | * More-specific error message when a component returns `nil` from 90 | `start` or `stop` ([commit fb891500]; reported by James Gatannah 91 | at [#17]) 92 | 93 | ### Version [0.2.2] released on September 7, 2014 94 | 95 | * System maps print as `#` to avoid trying to print huge 96 | objects in the REPL (reported by Howard Lewis Ship at [#9]) 97 | 98 | * Add error helpers `ex-component?` and `ex-without-components` 99 | 100 | * Change `:component-key` to `:system-key` in `ex-data` maps: 101 | breaking change for code which depended on the value of 102 | `:component-key` 103 | 104 | * Add `:system-key` to `ex-data` map from `try-action` 105 | 106 | * Minor changes to exception message strings 107 | 108 | * Leiningen profiles / aliases to test on all supported Clojure 109 | versions 110 | 111 | ### Version [0.2.1] released on December 17, 2013 112 | 113 | * Add generic `system-map` 114 | 115 | * More descriptive messages on exceptions 116 | 117 | * Add arity-1 versions of `start-system` and `stop-system` that 118 | default to all keys in the system 119 | 120 | ### Version [0.2.0] released on November 20, 2013 121 | 122 | * API compatible with 0.1.0 123 | 124 | * Some exception messages changed 125 | 126 | * Added default no-op implementation of Lifecycle protocol 127 | 128 | * Added `update-system` and `update-system-reverse` 129 | 130 | * Redefined `start-system` and `stop-system` in terms of these 131 | 132 | * `stop-system` now assoc's dependencies just like `start-system` 133 | 134 | 135 | 136 | ## 0.1.x series 137 | 138 | ### Version [0.1.0] released on October 28, 2013 139 | 140 | 141 | [1.2.0]: https://github.com/stuartsierra/component/tree/component-1.2.0 142 | [1.1.0]: https://github.com/stuartsierra/component/tree/component-1.1.0 143 | [1.0.0]: https://github.com/stuartsierra/component/tree/component-1.0.0 144 | [0.4.0]: https://github.com/stuartsierra/component/tree/component-0.4.0 145 | [0.3.2]: https://github.com/stuartsierra/component/tree/component-0.3.2 146 | [0.3.1]: https://github.com/stuartsierra/component/tree/component-0.3.1 147 | [0.3.0]: https://github.com/stuartsierra/component/tree/component-0.3.0 148 | [0.2.3]: https://github.com/stuartsierra/component/tree/component-0.2.3 149 | [0.2.2]: https://github.com/stuartsierra/component/tree/component-0.2.2 150 | [0.2.1]: https://github.com/stuartsierra/component/tree/component-0.2.1 151 | [0.2.0]: https://github.com/stuartsierra/component/tree/component-0.2.0 152 | [0.1.0]: https://github.com/stuartsierra/component/tree/component-0.1.0 153 | 154 | [commit fb891500]: https://github.com/stuartsierra/component/commit/fb891500506b048bd8d9d689dfd3ed8c0e940944 155 | [commit 69e62854]: https://github.com/stuartsierra/component/commit/69e62854c7dac7b4743a542e04ce4aa23a934c07 156 | [commit 7824f551]: https://github.com/stuartsierra/component/commit/7824f55129337c775a776daf6286fd43b8911b38 157 | [commit 5af4ad06]: https://github.com/stuartsierra/component/commit/5af4ad06fdc3ff3240573ae9394da92d8cf90c7e 158 | 159 | [#6]: https://github.com/stuartsierra/component/issues/6 160 | [#7]: https://github.com/stuartsierra/component/issues/7 161 | [#9]: https://github.com/stuartsierra/component/issues/9 162 | [#17]: https://github.com/stuartsierra/component/issues/17 163 | [#40]: https://github.com/stuartsierra/component/issues/40 164 | [#62]: https://github.com/stuartsierra/component/pull/62 165 | [#63]: https://github.com/stuartsierra/component/issues/63 166 | [#65]: https://github.com/stuartsierra/component/issues/65 167 | [#69]: https://github.com/stuartsierra/component/issues/69 168 | [#76]: https://github.com/stuartsierra/component/pull/76 169 | 170 | [dependency]: https://github.com/stuartsierra/dependency 171 | [tools.namespace]: https://github.com/clojure/tools.namespace 172 | 173 | [extend-via-metadata]: https://github.com/clojure/clojure/blob/28b87d53909774af28f9f9ba6dfa2d4b94194a57/changes.md#22-protocol-extension-by-metadata 174 | [ClojureScript 1.10.516]: https://github.com/clojure/clojurescript/blob/8a5abc4e02c72d000204674f38c6665c786302a4/changes.md 175 | -------------------------------------------------------------------------------- /dev/examples.clj: -------------------------------------------------------------------------------- 1 | (ns examples 2 | (:require [com.stuartsierra.component :as component])) 3 | 4 | ;;; Dummy functions to use in the examples 5 | 6 | (defn connect-to-database [host port] 7 | (println ";; Opening database connection") 8 | (reify java.io.Closeable 9 | (close [_] (println ";; Closing database connection")))) 10 | 11 | (defn execute-query [& _] 12 | (println ";; execute-query")) 13 | 14 | (defn execute-insert [& _] 15 | (println ";; execute-insert")) 16 | 17 | (defn new-scheduler [] 18 | (reify component/Lifecycle 19 | (start [this] 20 | (println ";; Starting scheduler") 21 | this) 22 | (stop [this] 23 | (println ";; Stopping scheduler") 24 | this))) 25 | 26 | 27 | ;;; Example database component 28 | 29 | ;; To define a component, define a Clojure record that implements the 30 | ;; `Lifecycle` protocol. 31 | 32 | (defrecord Database [host port connection] 33 | ;; Implement the Lifecycle protocol 34 | component/Lifecycle 35 | 36 | (start [component] 37 | (println ";; Starting database") 38 | ;; In the 'start' method, initialize this component 39 | ;; and start it running. For example, connect to a 40 | ;; database, create thread pools, or initialize shared 41 | ;; state. 42 | (let [conn (connect-to-database host port)] 43 | ;; Return an updated version of the component with 44 | ;; the run-time state assoc'd in. 45 | (assoc component :connection conn))) 46 | 47 | (stop [component] 48 | (println ";; Stopping database") 49 | ;; In the 'stop' method, shut down the running 50 | ;; component and release any external resources it has 51 | ;; acquired. 52 | (.close connection) 53 | ;; Return the component, optionally modified. Remember that if you 54 | ;; dissoc one of a record's base fields, you get a plain map. 55 | (assoc component :connection nil))) 56 | 57 | ;; Optionally, provide a constructor function that takes in 58 | ;; the essential configuration parameters of the component, 59 | ;; leaving the runtime state blank. 60 | 61 | (defn new-database [host port] 62 | (map->Database {:host host :port port})) 63 | 64 | ;; Define the functions implementing the behavior of the 65 | ;; component to take the component itself as an argument. 66 | 67 | (defn get-user [database username] 68 | (execute-query (:connection database) 69 | "SELECT * FROM users WHERE username = ?" 70 | username)) 71 | 72 | (defn add-user [database username favorite-color] 73 | (execute-insert (:connection database) 74 | "INSERT INTO users (username, favorite_color)" 75 | username favorite-color)) 76 | 77 | 78 | ;;; Second Example Component 79 | 80 | ;; Define other components in terms of the components on which they 81 | ;; depend. 82 | 83 | (defrecord ExampleComponent [options cache database scheduler] 84 | component/Lifecycle 85 | 86 | (start [this] 87 | (println ";; Starting ExampleComponent") 88 | ;; In the 'start' method, a component may assume that its 89 | ;; dependencies are available and have already been started. 90 | (assoc this :admin (get-user database "admin"))) 91 | 92 | (stop [this] 93 | (println ";; Stopping ExampleComponent") 94 | ;; Likewise, in the 'stop' method, a component may assume that its 95 | ;; dependencies will not be stopped until AFTER it is stopped. 96 | this)) 97 | 98 | ;; Not all the dependencies need to be supplied at construction time. 99 | ;; In general, the constructor should not depend on other components 100 | ;; being available or started. 101 | 102 | (defn example-component [config-options] 103 | (map->ExampleComponent {:options config-options 104 | :cache (atom {})})) 105 | 106 | 107 | ;;; Example System 108 | 109 | ;; Components are composed into systems. A system is a component which 110 | ;; knows how to start and stop other components. 111 | 112 | ;; A system can use the helper functions `start-system` and `stop-system`, 113 | ;; which take a set of keys naming components in the system to be 114 | ;; started/stopped. Order of the keys doesn't matter here. 115 | 116 | (def example-system-components [:scheduler :app :db]) 117 | 118 | (defrecord ExampleSystem [config-options db scheduler app] 119 | component/Lifecycle 120 | (start [this] 121 | (component/start-system this example-system-components)) 122 | (stop [this] 123 | (component/stop-system this example-system-components))) 124 | 125 | ;; When constructing the system, specify the dependency relationships 126 | ;; among components with the `using` function. 127 | 128 | (defn example-system [config-options] 129 | (let [{:keys [host port]} config-options] 130 | (map->ExampleSystem 131 | {:config-options config-options 132 | :db (new-database host port) 133 | :scheduler (new-scheduler) 134 | :app (component/using 135 | (example-component config-options) 136 | {:database :db 137 | :scheduler :scheduler})}))) 138 | ;; ^ ^ 139 | ;; | | 140 | ;; | \- Keys in the ExampleSystem record 141 | ;; | 142 | ;; \- Keys in the ExampleComponent record 143 | 144 | ;; `using` takes a component and a map telling the system where to 145 | ;; find that component's dependencies. Keys in the map are the keys in 146 | ;; the component record itself, values are the map are the 147 | ;; corresponding keys in the system record. 148 | 149 | ;; Based on this information (stored as metadata on the component 150 | ;; records) the `start-system` function will construct a dependency 151 | ;; graph of the components, assoc in their dependencies, and start 152 | ;; them all in the correct order. 153 | 154 | ;; Optionally, if the keys in the system map are the same as in the 155 | ;; component map, they may be passed as a vector to `using`. If you 156 | ;; know all the dependencies in advance, you may even add the metadata 157 | ;; in the component's constructor: 158 | 159 | (defrecord AnotherComponent [component-a component-b]) 160 | 161 | (defrecord AnotherSystem [component-a component-b component-c]) 162 | 163 | (defn another-component [] 164 | (component/using 165 | (map->AnotherComponent {}) 166 | [:component-a :component-b])) 167 | 168 | 169 | 170 | ;; Sample usage: 171 | (comment 172 | 173 | (def system (example-system {:host "dbhost.com" :port 123})) 174 | ;;=> #'examples/system 175 | 176 | (alter-var-root #'system component/start) 177 | ;; Starting database 178 | ;; Opening database connection 179 | ;; Starting scheduler 180 | ;; Starting ExampleComponent 181 | ;; execute-query 182 | ;;=> #examples.ExampleSystem{ ... } 183 | 184 | (alter-var-root #'system component/stop) 185 | ;; Stopping ExampleComponent 186 | ;; Stopping scheduler 187 | ;; Stopping database 188 | ;; Closing database connection 189 | ;;=> #examples.ExampleSystem{ ... } 190 | 191 | 192 | ) 193 | 194 | ;; Local Variables: 195 | ;; clojure-defun-style-default-indent: t 196 | ;; End: 197 | -------------------------------------------------------------------------------- /test/com/stuartsierra/component_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.stuartsierra.component-test 2 | (:require [clojure.test :refer (deftest is are)] 3 | [clojure.set :refer (map-invert)] 4 | [com.stuartsierra.component :as component])) 5 | 6 | (def ^:dynamic *log* nil) 7 | 8 | (defn- log [& args] 9 | (when (thread-bound? #'*log*) 10 | (set! *log* (conj *log* args)))) 11 | 12 | (defn- ordering 13 | "Given an ordered collection of messages, returns a map from the 14 | head of each message to its index position in the collection." 15 | [log] 16 | (into {} (map-indexed (fn [i [message & _]] [message i]) log))) 17 | 18 | (defn before? 19 | "In the collection of messages, does the message beginning with 20 | symbol a come before the message begging with symbol b?" 21 | [log sym-a sym-b] 22 | (let [order (ordering log)] 23 | (< (get order sym-a) (get order sym-b)))) 24 | 25 | (defn started? [component] 26 | (true? (::started? component))) 27 | 28 | (defn stopped? [component] 29 | (false? (::started? component))) 30 | 31 | (defrecord ComponentA [state] 32 | component/Lifecycle 33 | (start [this] 34 | (log 'ComponentA.start this) 35 | (assoc this ::started? true)) 36 | (stop [this] 37 | (log 'ComponentA.stop this) 38 | (assoc this ::started? false))) 39 | 40 | (defn component-a [] 41 | (->ComponentA (rand-int Integer/MAX_VALUE))) 42 | 43 | (defrecord ComponentB [state a] 44 | component/Lifecycle 45 | (start [this] 46 | (log 'ComponentB.start this) 47 | (assert (started? a)) 48 | (assoc this ::started? true)) 49 | (stop [this] 50 | (log 'ComponentB.stop this) 51 | (assert (started? a)) 52 | (assoc this ::started? false))) 53 | 54 | (defn component-b [] 55 | (component/using 56 | (map->ComponentB {:state (rand-int Integer/MAX_VALUE)}) 57 | [:a])) 58 | 59 | (defrecord ComponentC [state a b] 60 | component/Lifecycle 61 | (start [this] 62 | (log 'ComponentC.start this) 63 | (assert (started? a)) 64 | (assert (started? b)) 65 | (assoc this ::started? true)) 66 | (stop [this] 67 | (log 'ComponentC.stop this) 68 | (assert (started? a)) 69 | (assert (started? b)) 70 | (assoc this ::started? false))) 71 | 72 | (defn component-c [] 73 | (component/using 74 | (map->ComponentC {:state (rand-int Integer/MAX_VALUE)}) 75 | [:a :b])) 76 | 77 | (defrecord ComponentD [state my-c b] 78 | component/Lifecycle 79 | (start [this] 80 | (log 'ComponentD.start this) 81 | (assert (started? b)) 82 | (assert (started? my-c)) 83 | (assoc this ::started? true)) 84 | (stop [this] 85 | (log 'ComponentD.stop this) 86 | (assert (started? b)) 87 | (assert (started? my-c)) 88 | (assoc this ::started? false))) 89 | 90 | (defn component-d [] 91 | (map->ComponentD {:state (rand-int Integer/MAX_VALUE)})) 92 | 93 | (defrecord ComponentE [state] 94 | component/Lifecycle 95 | (start [this] 96 | (log 'ComponentE.start this) 97 | (assoc this ::started? true)) 98 | (stop [this] 99 | (log 'ComponentE.stop this) 100 | (assoc this ::started? false))) 101 | 102 | (defn component-e [] 103 | (map->ComponentE {:state (rand-int Integer/MAX_VALUE)})) 104 | 105 | (defrecord System1 [d a e c b] ; deliberately scrambled order 106 | component/Lifecycle 107 | (start [this] 108 | (log 'System1.start this) 109 | (component/start-system this)) 110 | (stop [this] 111 | (log 'System1.stop this) 112 | (component/stop-system this))) 113 | 114 | (defn system-1 [] 115 | (map->System1 {:a (component-a) 116 | :b (component-b) 117 | :c (component-c) 118 | :d (component/using (component-d) 119 | {:b :b 120 | :my-c :c}) 121 | :e (component-e)})) 122 | 123 | (defmacro with-log [& body] 124 | `(binding [*log* []] 125 | ~@body 126 | *log*)) 127 | 128 | (deftest components-start-in-order 129 | (let [log (with-log (component/start (system-1)))] 130 | (are [k1 k2] (before? log k1 k2) 131 | 'ComponentA.start 'ComponentB.start 132 | 'ComponentA.start 'ComponentC.start 133 | 'ComponentB.start 'ComponentC.start 134 | 'ComponentC.start 'ComponentD.start 135 | 'ComponentB.start 'ComponentD.start))) 136 | 137 | (deftest all-components-started 138 | (let [system (component/start (system-1))] 139 | (doseq [component (vals system)] 140 | (is (started? component))))) 141 | 142 | (deftest all-components-stopped 143 | (let [system (component/stop (component/start (system-1)))] 144 | (doseq [component (vals system)] 145 | (is (stopped? component))))) 146 | 147 | (deftest dependencies-satisfied 148 | (let [system (component/start (component/start (system-1)))] 149 | (are [keys] (started? (get-in system keys)) 150 | [:b :a] 151 | [:c :a] 152 | [:c :b] 153 | [:d :my-c]))) 154 | 155 | (defrecord ErrorStartComponentC [state error a b] 156 | component/Lifecycle 157 | (start [this] 158 | (throw error)) 159 | (stop [this] 160 | this)) 161 | 162 | (defn error-start-c [error] 163 | (component/using 164 | (map->ErrorStartComponentC {:error error}) 165 | [:a :b])) 166 | 167 | (defn setup-error 168 | ([] 169 | (setup-error (ex-info "Boom!" {}))) 170 | ([error] 171 | (try (component/start 172 | (assoc (system-1) :c (error-start-c error))) 173 | (catch Exception e e)))) 174 | 175 | (deftest error-thrown-with-partial-system 176 | (let [ex (setup-error)] 177 | (is (started? (-> ex ex-data :system :b :a))))) 178 | 179 | (deftest error-thrown-with-component-dependencies 180 | (let [ex (setup-error)] 181 | (is (started? (-> ex ex-data :component :a))) 182 | (is (started? (-> ex ex-data :component :b))))) 183 | 184 | (deftest error-thrown-with-cause 185 | (let [error (ex-info "Boom!" {}) 186 | ex (setup-error error)] 187 | (is (identical? error (.getCause ^Exception ex))))) 188 | 189 | (deftest error-is-from-component 190 | (let [error (ex-info "Boom!" {}) 191 | ex (setup-error error)] 192 | (is (component/ex-component? ex)))) 193 | 194 | (deftest error-is-not-from-component 195 | (is (not (component/ex-component? (ex-info "Boom!" {}))))) 196 | 197 | (deftest remove-components-from-error 198 | (let [error (ex-info (str (rand-int Integer/MAX_VALUE)) {}) 199 | ^Exception ex (setup-error error) 200 | ^Exception ex-without (component/ex-without-components ex)] 201 | (is (contains? (ex-data ex) :component)) 202 | (is (contains? (ex-data ex) :system)) 203 | (is (not (contains? (ex-data ex-without) :component))) 204 | (is (not (contains? (ex-data ex-without) :system))) 205 | (is (= (.getMessage ex) 206 | (.getMessage ex-without))) 207 | (is (= (.getCause ex) 208 | (.getCause ex-without))) 209 | (is (java.util.Arrays/equals 210 | (.getStackTrace ex) 211 | (.getStackTrace ex-without))))) 212 | 213 | (defrecord System2b [one] 214 | component/Lifecycle 215 | (start [this] 216 | (assert (started? (get-in one [:b :a]))) 217 | this) 218 | (stop [this] 219 | (assert (started? (get-in one [:b :a]))) 220 | this)) 221 | 222 | (defn system-2 [] 223 | (component/system-map :alpha (system-1) 224 | :beta (component/using (->System2b nil) 225 | {:one :alpha}))) 226 | 227 | (deftest composed-systems 228 | (let [system (component/start (system-2))] 229 | (is (started? (get-in system [:beta :one :d :my-c]))))) 230 | 231 | (defn increment-all-components [system] 232 | (component/update-system 233 | system (keys system) update-in [:n] inc)) 234 | 235 | (defn assert-increments [system] 236 | (are [n keys] (= n (get-in system keys)) 237 | 11 [:a :n] 238 | 11 [:b :a :n] 239 | 11 [:c :a :n] 240 | 11 [:c :b :a :n] 241 | 11 [:e :d :b :a :n] 242 | 21 [:b :n] 243 | 21 [:c :b :n] 244 | 21 [:d :b :n] 245 | 31 [:c :n] 246 | 41 [:d :n] 247 | 51 [:e :n])) 248 | 249 | (deftest update-with-custom-function-on-maps 250 | (let [system {:a {:n 10} 251 | :b (component/using {:n 20} [:a]) 252 | :c (component/using {:n 30} [:a :b]) 253 | :d (component/using {:n 40} [:a :b]) 254 | :e (component/using {:n 50} [:b :c :d])}] 255 | (assert-increments (increment-all-components system)))) 256 | 257 | (deftest t-system-using 258 | (let [dependency-map {:b [:a] 259 | :c [:a :b] 260 | :d {:a :a :b :b} 261 | :e [:b :c :d]} 262 | system {:a {:n 10} 263 | :b {:n 20} 264 | :c {:n 30} 265 | :d {:n 40} 266 | :e {:n 50}} 267 | system (component/system-using system dependency-map)] 268 | (assert-increments (increment-all-components system)))) 269 | 270 | (defrecord ComponentWithoutLifecycle [state]) 271 | 272 | (deftest component-without-lifecycle 273 | (let [c (->ComponentWithoutLifecycle nil)] 274 | (is (= c (component/start c))) 275 | (is (= c (component/stop c))))) 276 | 277 | (defrecord ComponentReturningNil [state] 278 | component/Lifecycle 279 | (start [this] 280 | nil) 281 | (stop [this] 282 | nil)) 283 | 284 | (deftest component-returning-nil 285 | (let [a (->ComponentReturningNil nil) 286 | s (component/system-map :a a :b (component-b)) 287 | e (try (component/start s) 288 | false 289 | (catch Exception e e))] 290 | (is (= ::component/nil-component (:reason (ex-data e)))))) 291 | 292 | (deftest missing-dependency-error 293 | (let [system-key ::system-b 294 | local-key ::local-b 295 | a (component/using (component-a) {local-key system-key}) 296 | system (component/system-map 297 | :a a) 298 | e (try (component/start system) 299 | false 300 | (catch Exception e e)) 301 | data (ex-data e)] 302 | (is (= ::component/missing-dependency (:reason data))) 303 | (is (= system-key (:system-key data))) 304 | (is (= local-key (:dependency-key data))) 305 | (is (= a (:component data))) 306 | (is (= system (:system data))))) 307 | 308 | (deftest t-subsystem 309 | (let [system (component/system-map 310 | :d (component-d) 311 | :e (component-e) 312 | :f (component/using {} [:d]) 313 | :g (component/using {} [:f])) 314 | subsystem (component/subsystem system [:g])] 315 | (is (= (type system) 316 | (type subsystem))) 317 | (is (= #{:d :f :g} (set (keys subsystem)))))) 318 | -------------------------------------------------------------------------------- /src/com/stuartsierra/component.cljc: -------------------------------------------------------------------------------- 1 | (ns com.stuartsierra.component 2 | (:require [com.stuartsierra.dependency :as dep] 3 | [com.stuartsierra.component.platform :as platform])) 4 | 5 | (defprotocol Lifecycle 6 | :extend-via-metadata true 7 | (start [component] 8 | "Begins operation of this component. Synchronous, does not return 9 | until the component is started. Returns an updated version of this 10 | component.") 11 | (stop [component] 12 | "Ceases operation of this component. Synchronous, does not return 13 | until the component is stopped. Returns an updated version of this 14 | component.")) 15 | 16 | ;; No-op implementation if one is not defined. 17 | (extend-protocol Lifecycle 18 | #?(:clj java.lang.Object :cljs default) 19 | (start [this] 20 | this) 21 | (stop [this] 22 | this)) 23 | 24 | (defn dependencies 25 | "Returns the map of other components on which this component depends." 26 | [component] 27 | (::dependencies (meta component) {})) 28 | 29 | (defn using 30 | "Associates metadata with component describing the other components 31 | on which it depends. Component dependencies are specified as a map. 32 | Keys in the map correspond to keys in this component which must be 33 | provided by its containing system. Values in the map are the keys in 34 | the system at which those components may be found. Alternatively, if 35 | the keys are the same in both the component and its enclosing 36 | system, they may be specified as a vector of keys." 37 | [component dependencies] 38 | (vary-meta 39 | component update-in [::dependencies] (fnil merge {}) 40 | (cond 41 | (map? dependencies) 42 | dependencies 43 | (vector? dependencies) 44 | (into {} (map (fn [x] [x x]) dependencies)) 45 | :else 46 | (throw (ex-info "Dependencies must be a map or vector" 47 | {:reason ::invalid-dependencies 48 | :component component 49 | :dependencies dependencies}))))) 50 | 51 | (defn- nil-component [system key] 52 | (ex-info (str "Component " key " was nil in system; maybe it returned nil from start or stop") 53 | {:reason ::nil-component 54 | :system-key key 55 | :system system})) 56 | 57 | (defn- get-component [system key] 58 | (let [component (get system key ::not-found)] 59 | (when (nil? component) 60 | (throw (nil-component system key))) 61 | (when (= ::not-found component) 62 | (throw (ex-info (str "Missing component " key " from system") 63 | {:reason ::missing-component 64 | :system-key key 65 | :system system}))) 66 | component)) 67 | 68 | (defn- get-dependency [system system-key component dependency-key] 69 | (let [dependency (get system system-key ::not-found)] 70 | (when (nil? dependency) 71 | (throw (nil-component system system-key))) 72 | (when (= ::not-found dependency) 73 | (throw (ex-info (str "Missing dependency " dependency-key 74 | " of " (platform/type-name component) 75 | " expected in system at " system-key) 76 | {:reason ::missing-dependency 77 | :system-key system-key 78 | :dependency-key dependency-key 79 | :component component 80 | :system system}))) 81 | dependency)) 82 | 83 | (defn system-using 84 | "Associates dependency metadata with multiple components in the 85 | system. dependency-map is a map of keys in the system to maps or 86 | vectors specifying the dependencies of the component at that key in 87 | the system, as per 'using'." 88 | [system dependency-map] 89 | (reduce-kv 90 | (fn [system key dependencies] 91 | (let [component (get-component system key)] 92 | (assoc system key (using component dependencies)))) 93 | system 94 | dependency-map)) 95 | 96 | (defn dependency-graph 97 | "Returns a dependency graph, using the data structures defined in 98 | com.stuartsierra.dependency, for the components found by 99 | (select-keys system component-keys)" 100 | [system component-keys] 101 | (reduce-kv (fn [graph key component] 102 | (reduce #(dep/depend %1 key %2) 103 | graph 104 | (vals (dependencies component)))) 105 | (dep/graph) 106 | (select-keys system component-keys))) 107 | 108 | (defn- assoc-dependency [system component dependency-key system-key] 109 | (let [dependency (get-dependency system system-key component dependency-key)] 110 | (assoc component dependency-key dependency))) 111 | 112 | (defn- assoc-dependencies [component system] 113 | (reduce-kv #(assoc-dependency system %1 %2 %3) 114 | component 115 | (dependencies component))) 116 | 117 | (defn- try-action [component system key f args] 118 | (try (apply f component args) 119 | (catch #?(:clj Throwable :cljs :default) t 120 | (throw (ex-info (str "Error in component " key 121 | " in system " (platform/type-name system) 122 | " calling " f) 123 | {:reason ::component-function-threw-exception 124 | :function f 125 | :system-key key 126 | :component component 127 | :system system} 128 | t))))) 129 | 130 | (defn update-system 131 | "Invokes (apply f component args) on each of the components at 132 | component-keys in the system, in dependency order. Before invoking 133 | f, assoc's updated dependencies of the component." 134 | [system component-keys f & args] 135 | (let [graph (dependency-graph system component-keys)] 136 | (reduce (fn [system key] 137 | (assoc system key 138 | (-> (get-component system key) 139 | (assoc-dependencies system) 140 | (try-action system key f args)))) 141 | system 142 | (sort (dep/topo-comparator graph) component-keys)))) 143 | 144 | (defn update-system-reverse 145 | "Like update-system but operates in reverse dependency order." 146 | [system component-keys f & args] 147 | (let [graph (dependency-graph system component-keys)] 148 | (reduce (fn [system key] 149 | (assoc system key 150 | (-> (get-component system key) 151 | (assoc-dependencies system) 152 | (try-action system key f args)))) 153 | system 154 | (reverse (sort (dep/topo-comparator graph) component-keys))))) 155 | 156 | (defn start-system 157 | "Recursively starts all components in the system, in dependency 158 | order, assoc'ing in their dependencies along the way. 159 | 160 | The 2-argument arity selects which components to start, but this is 161 | a historical implementation detail. Use `subsystem` instead." 162 | ([system] 163 | (start-system system (keys system))) 164 | ([system component-keys] 165 | (update-system system component-keys #'start))) 166 | 167 | (defn stop-system 168 | "Recursively stops all components in the system, in reverse 169 | dependency order. 170 | 171 | The 2-argument arity selects which components to start, but this is 172 | a historical implementation detail. Use `subsystem` instead." 173 | ([system] 174 | (stop-system system (keys system))) 175 | ([system component-keys] 176 | (update-system-reverse system component-keys #'stop))) 177 | 178 | (defrecord SystemMap [] 179 | Lifecycle 180 | (start [system] 181 | (start-system system)) 182 | (stop [system] 183 | (stop-system system)) 184 | #?@(:clj [java.io.Closeable 185 | (close [system] 186 | (stop system))])) 187 | 188 | #?(:clj 189 | (defmethod clojure.core/print-method SystemMap 190 | [system ^java.io.Writer writer] 191 | (.write writer "#")) 192 | :cljs 193 | (extend-protocol IPrintWithWriter 194 | SystemMap 195 | (-pr-writer [this writer opts] 196 | (-write writer "#")))) 197 | 198 | (defn system-map 199 | "Returns a system constructed of key/value pairs. The system has 200 | default implementations of the Lifecycle 'start' and 'stop' methods 201 | which recursively start/stop all components in the system. 202 | 203 | System maps print as # to avoid overwhelming the printer 204 | with large objects. As a consequence, printed system maps cannot be 205 | 'read'. To disable this behavior and print system maps like normal 206 | records, call 207 | (remove-method clojure.core/print-method com.stuartsierra.component.SystemMap)" 208 | [& keyvals] 209 | ;; array-map doesn't check argument length (CLJ-1319) 210 | (when-not (even? (count keyvals)) 211 | (throw (platform/argument-error 212 | "system-map requires an even number of arguments"))) 213 | (map->SystemMap (apply array-map keyvals))) 214 | 215 | (defn subsystem 216 | "Returns a system containing only components associated with the keys 217 | in subsystem-keys, along with all of their transitive dependencies." 218 | [system subsystem-keys] 219 | (let [graph (dependency-graph system (keys system)) 220 | selected (into (set subsystem-keys) 221 | (mapcat #(dep/transitive-dependencies graph %)) 222 | subsystem-keys)] 223 | (merge (system-map) 224 | (select-keys system selected)))) 225 | 226 | (defn ex-component? 227 | "True if the error has ex-data indicating it was thrown by something 228 | in the com.stuartsierra.component namespace." 229 | [error] 230 | (let [{:keys [reason]} (ex-data error)] 231 | (and (keyword? reason) 232 | (= "com.stuartsierra.component" 233 | (namespace reason))))) 234 | 235 | (defn ex-without-components 236 | "If the error has ex-data provided by the com.stuartsierra.component 237 | namespace, returns a new exception instance with the :component 238 | and :system removed from its ex-data. Preserves the other details of 239 | the original error. If the error was not created by this namespace, 240 | returns it unchanged. Use this when you want to catch and rethrow 241 | exceptions without including the full component or system." 242 | [error] 243 | (if (ex-component? error) 244 | (platform/alter-ex-data error dissoc :component :system) 245 | error)) 246 | 247 | ;; Copyright © 2015 Stuart Sierra 248 | 249 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy of 250 | ;; this software and associated documentation files (the "Software"), to deal in 251 | ;; the Software without restriction, including without limitation the rights to 252 | ;; use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 253 | ;; the Software, and to permit persons to whom the Software is furnished to do so, 254 | ;; subject to the following conditions: 255 | 256 | ;; The above copyright notice and this permission notice shall be included in all 257 | ;; copies or substantial portions of the Software. 258 | 259 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 260 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 261 | ;; FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 262 | ;; COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 263 | ;; IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 264 | ;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 265 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Component 2 | 3 | 'Component' is a tiny Clojure framework for managing the lifecycle and 4 | dependencies of software components which have runtime state. 5 | 6 | This is primarily a design pattern with a few helper functions. It can 7 | be seen as a style of dependency injection using immutable data 8 | structures. 9 | 10 | See the [video from Clojure/West 2014](https://www.youtube.com/watch?v=13cmHf_kt-Q) (YouTube, 40 minutes) 11 | 12 | 13 | ## Releases and Dependency Information 14 | 15 | * I publish releases to [Clojars] 16 | 17 | * Latest stable release is [1.2.0](https://github.com/stuartsierra/component/tree/component-1.2.0) 18 | 19 | * [All releases](https://clojars.org/com.stuartsierra/component) 20 | 21 | * [Change Log](CHANGES.md) 22 | 23 | [Leiningen] dependency information: 24 | 25 | [com.stuartsierra/component "1.2.0"] 26 | 27 | [deps.edn] dependency information: 28 | 29 | com.stuartsierra/component {:mvn/version "1.2.0"} 30 | 31 | [Maven] dependency information: 32 | 33 | 34 | com.stuartsierra 35 | component 36 | 1.2.0 37 | 38 | 39 | [Clojars]: http://clojars.org/ 40 | [deps.edn]: http://www.gradle.org/ 41 | [Leiningen]: http://leiningen.org/ 42 | [Maven]: http://maven.apache.org/ 43 | 44 | 45 | 46 | ## Dependencies and Compatibility 47 | 48 | Starting with version 0.3.0 of 'Component', Clojure or ClojureScript 49 | version 1.7.0 or higher is required for Conditional Read support. 50 | 51 | Version 0.2.3 of 'Component' is compatible with 52 | Clojure versions 1.4.0 and higher. 53 | 54 | 'Component' requires my [dependency] library 55 | 56 | [dependency]: https://github.com/stuartsierra/dependency 57 | 58 | 59 | 60 | ## Discussion 61 | 62 | Please post questions on the [Clojure Mailing List](https://groups.google.com/forum/#!forum/clojure) 63 | 64 | 65 | 66 | ## Introduction 67 | 68 | For the purposes of this framework, a *component* is a collection of 69 | functions or procedures which share some runtime state. 70 | 71 | Some examples of components: 72 | 73 | * Database access: query and insert functions sharing a database 74 | connection 75 | 76 | * External API service: functions to send and retrieve data sharing an 77 | HTTP connection pool 78 | 79 | * Web server: functions to handle different routes sharing all the 80 | runtime state of the web application, such as a session store 81 | 82 | * In-memory cache: functions to get and set data in a shared mutable 83 | reference such as a Clojure Atom or Ref 84 | 85 | A *component* is similar in spirit to the definition of an *object* in 86 | Object-Oriented Programming. This does not alter the primacy of pure 87 | functions and immutable data structures in Clojure as a language. Most 88 | functions are just functions, and most data are just data. Components 89 | are intended to help manage stateful resources within a functional 90 | paradigm. 91 | 92 | 93 | ### Advantages of the Component Model 94 | 95 | Large applications often consist of many stateful processes which must 96 | be started and stopped in a particular order. The component model 97 | makes those relationships explicit and declarative, instead of 98 | implicit in imperative code. 99 | 100 | Components provide some basic guidance for structuring a Clojure 101 | application, with boundaries between different parts of a system. 102 | Components offer some encapsulation, in the sense of grouping together 103 | related entities. Each component receives references only to the 104 | things it needs, avoiding unnecessary shared state. Instead of 105 | reaching through multiple levels of nested maps, a component can have 106 | everything it needs at most one map lookup away. 107 | 108 | Instead of having mutable state (atoms, refs, etc.) scattered 109 | throughout different namespaces, all the stateful parts of an 110 | application can be gathered together. In some cases, using components 111 | may eliminate the need for mutable references altogether, for example 112 | to store the "current" connection to a resource such as a database. At 113 | the same time, having all state reachable via a single "system" object 114 | makes it easy to reach in and inspect any part of the application from 115 | the REPL. 116 | 117 | The component dependency model makes it easy to swap in "stub" or 118 | "mock" implementations of a component for testing purposes, without 119 | relying on time-dependent constructs, such as `with-redefs` or 120 | `binding`, which are often subject to race conditions in 121 | multi-threaded code. 122 | 123 | Having a coherent way to set up and tear down **all** the state 124 | associated with an application enables rapid development cycles 125 | without restarting the JVM. It can also make unit tests faster and 126 | more independent, since the cost of creating and starting a system is 127 | low enough that every test can create a new instance of the system. 128 | 129 | 130 | ### Disadvantages of the Component Model 131 | 132 | First and foremost, this framework works best when all parts of an 133 | application follow the same pattern. It is not easy to retrofit the 134 | component model to an existing application without major refactoring. 135 | 136 | In particular, the 'component' library assumes that all application 137 | state is passed as arguments to the functions that use it. As a 138 | result, this framework may be awkward to use with code which relies on 139 | global or singleton references. 140 | 141 | For small applications, declaring the dependency relationships among 142 | components may actually be more work than manually starting all the 143 | components in the correct order. You can still use the 'Lifecycle' 144 | protocol without using the dependency-injection features, but the 145 | added value of 'component' in that case is small. 146 | 147 | The "system object" produced by this framework is a large and complex 148 | map with a lot of duplication. The same component may appear in 149 | multiple places in the map. The actual memory cost of this duplication 150 | is negligible due to persistent data structures, but the system map is 151 | typically too large to inspect visually. 152 | 153 | You must explicitly specify all the dependency relationships among 154 | components: the code cannot discover these relationships 155 | automatically. 156 | 157 | Finally, the 'component' library forbids cyclic dependencies among 158 | components. I believe that cyclic dependencies usually indicate 159 | architectural flaws and can be eliminated by restructuring the 160 | application. In the rare case where a cyclic dependency cannot be 161 | avoided, you can use mutable references to manage it, but this is 162 | outside the scope of 'component'. 163 | 164 | 165 | 166 | 167 | ## Usage 168 | 169 | ```clojure 170 | (ns com.example.your-application 171 | (:require [com.stuartsierra.component :as component])) 172 | ``` 173 | 174 | 175 | ### Creating Components 176 | 177 | To create a component, define a Clojure record that implements the 178 | `Lifecycle` protocol. 179 | 180 | ```clojure 181 | (defrecord Database [host port connection] 182 | ;; Implement the Lifecycle protocol 183 | component/Lifecycle 184 | 185 | (start [component] 186 | (println ";; Starting database") 187 | ;; In the 'start' method, initialize this component 188 | ;; and start it running. For example, connect to a 189 | ;; database, create thread pools, or initialize shared 190 | ;; state. 191 | (let [conn (connect-to-database host port)] 192 | ;; Return an updated version of the component with 193 | ;; the run-time state assoc'd in. 194 | (assoc component :connection conn))) 195 | 196 | (stop [component] 197 | (println ";; Stopping database") 198 | ;; In the 'stop' method, shut down the running 199 | ;; component and release any external resources it has 200 | ;; acquired. 201 | (.close connection) 202 | ;; Return the component, optionally modified. Remember that if you 203 | ;; dissoc one of a record's base fields, you get a plain map. 204 | (assoc component :connection nil))) 205 | ``` 206 | 207 | Optionally, provide a constructor function that takes arguments for 208 | the essential configuration parameters of the component, leaving the 209 | runtime state blank. 210 | 211 | ```clojure 212 | (defn new-database [host port] 213 | (map->Database {:host host :port port})) 214 | ``` 215 | 216 | Define the functions implementing the behavior of the component to 217 | take an **instance** of the component as an argument. 218 | 219 | ```clojure 220 | (defn get-user [database username] 221 | (execute-query (:connection database) 222 | "SELECT * FROM users WHERE username = ?" 223 | username)) 224 | 225 | (defn add-user [database username favorite-color] 226 | (execute-insert (:connection database) 227 | "INSERT INTO users (username, favorite_color)" 228 | username favorite-color)) 229 | ``` 230 | 231 | Define other components in terms of the components on which they 232 | depend. 233 | 234 | ```clojure 235 | (defrecord ExampleComponent [options cache database scheduler] 236 | component/Lifecycle 237 | 238 | (start [this] 239 | (println ";; Starting ExampleComponent") 240 | ;; In the 'start' method, a component may assume that its 241 | ;; dependencies are available and have already been started. 242 | (assoc this :admin (get-user database "admin"))) 243 | 244 | (stop [this] 245 | (println ";; Stopping ExampleComponent") 246 | ;; Likewise, in the 'stop' method, a component may assume that its 247 | ;; dependencies will not be stopped until AFTER it is stopped. 248 | this)) 249 | ``` 250 | 251 | **Do not pass component dependencies in a constructor.** 252 | Systems are responsible for injecting runtime dependencies into the 253 | components they contain: see the next section. 254 | 255 | ```clojure 256 | (defn example-component [config-options] 257 | (map->ExampleComponent {:options config-options 258 | :cache (atom {})})) 259 | ``` 260 | 261 | 262 | ### Systems 263 | 264 | Components are composed into systems. A system is a component which 265 | knows how to start and stop other components. It is also responsible 266 | for injecting dependencies into the components which need them. 267 | 268 | The easiest way to create a system is with the `system-map` function, 269 | which takes a series of key/value pairs just like the `hash-map` or 270 | `array-map` constructors. Keys in the system map are keywords. Values 271 | in the system map are *instances* of components, usually records or 272 | maps. 273 | 274 | ```clojure 275 | (defn example-system [config-options] 276 | (let [{:keys [host port]} config-options] 277 | (component/system-map 278 | :db (new-database host port) 279 | :scheduler (new-scheduler) 280 | :app (component/using 281 | (example-component config-options) 282 | {:database :db 283 | :scheduler :scheduler})))) 284 | ``` 285 | 286 | Specify the dependency relationships among components with the `using` 287 | function. `using` takes a component and a collection of keys naming 288 | that component's dependencies. 289 | 290 | If the component and the system use the same keys, then you can 291 | specify dependencies as a *vector* of keys: 292 | 293 | ```clojure 294 | (component/system-map 295 | :database (new-database host port) 296 | :scheduler (new-scheduler) 297 | :app (component/using 298 | (example-component config-options) 299 | [:database :scheduler])) 300 | ;; Both ExampleComponent and the system have 301 | ;; keys :database and :scheduler 302 | ``` 303 | 304 | If the component and the system use *different* keys, then specify 305 | them as a map of `{:component-key :system-key}`. 306 | That is, the `using` keys match the keys in the component, 307 | the values match keys in the system. 308 | 309 | ```clojure 310 | (component/system-map 311 | :db (new-database host port) 312 | :sched (new-scheduler) 313 | :app (component/using 314 | (example-component config-options) 315 | {:database :db 316 | :scheduler :sched})) 317 | ;; ^ ^ 318 | ;; | | 319 | ;; | \- Keys in the system map 320 | ;; | 321 | ;; \- Keys in the ExampleComponent record 322 | ``` 323 | 324 | The system map provides its own implementation of the Lifecycle 325 | protocol which uses this dependency information (stored as metadata on 326 | each component) to start the components in the correct order. 327 | 328 | Before starting each component, the system will `assoc` its 329 | dependencies based on the metadata provided by `using`. 330 | 331 | Again using the example above, the ExampleComponent would be started 332 | *as if* you did this: 333 | 334 | ```clojure 335 | (-> example-component 336 | (assoc :database (:db system)) 337 | (assoc :scheduler (:sched system)) 338 | (start)) 339 | ``` 340 | 341 | Stop a system by calling the `stop` method on it. This will stop each 342 | component, in *reverse* dependency order, and then re-assoc the 343 | dependencies of each component. **Note:** `stop` is not the exact 344 | inverse of `start`; component dependencies will still be associated. 345 | 346 | It doesn't matter *when* you associate dependency metadata on a 347 | component, as long as it happens before you call `start`. If you know 348 | the names of all the components in your system in advance, you could 349 | choose to add the metadata in the component's constructor: 350 | 351 | ```clojure 352 | (defrecord AnotherComponent [component-a component-b]) 353 | 354 | (defrecord AnotherSystem [component-a component-b component-c]) 355 | 356 | (defn another-component [] ; constructor 357 | (component/using 358 | (map->AnotherComponent {}) 359 | [:component-a :component-b])) 360 | ``` 361 | 362 | Alternately, component dependencies can be specified all at once for 363 | all components in the system with `system-using`, which takes a map 364 | from component names to their dependencies. 365 | 366 | ```clojure 367 | (defn example-system [config-options] 368 | (let [{:keys [host port]} config-options] 369 | (-> (component/system-map 370 | :config-options config-options 371 | :db (new-database host port) 372 | :sched (new-scheduler) 373 | :app (example-component config-options)) 374 | (component/system-using 375 | {:app {:database :db 376 | :scheduler :sched}})))) 377 | ``` 378 | 379 | 380 | ### Entry Points in Production 381 | 382 | The 'component' library does not dictate how you store the system map 383 | or use the components it contains. That's up to you. 384 | 385 | The typical approach differs in development and production: 386 | 387 | In **production**, the system map is ephemeral. It is used to start 388 | all the components running, then it is discarded. 389 | 390 | When your application starts, for example in a `main` function, 391 | construct an instance of the system and call `component/start` on it. 392 | Then hand off control to one or more components that represent the 393 | "entry points" of your application. 394 | 395 | For example, you might have a web server component that starts 396 | listening for HTTP requests, or an event loop component that waits for 397 | input. Each of these components can create one or more threads in its 398 | Lifecycle `start` method. Then `main` could be as trivial as: 399 | 400 | ```clojure 401 | (defn main [] (component/start (new-system))) 402 | ``` 403 | 404 | **Note:** You will still need to keep the main thread of your 405 | application running to prevent the JVM from shutting down. One way is 406 | to block the main thread waiting for some signal to shut down; another 407 | way is to `Thread/join` the main thread to one of your components' 408 | threads. 409 | 410 | This also works well in conjunction with command-line drivers such as 411 | [Apache Commons Daemon](http://commons.apache.org/proper/commons-daemon/). 412 | 413 | 414 | ### Entry Points for Development 415 | 416 | In **development**, it is useful to have a reference to the system map 417 | to examine it from the REPL. 418 | 419 | The easiest way to do this is to `def` a Var to hold the system map in 420 | a development namespace. Use `alter-var-root` to start and stop it. 421 | 422 | Example REPL session: 423 | 424 | ```clojure 425 | (def system (example-system {:host "dbhost.com" :port 123})) 426 | ;;=> #'examples/system 427 | 428 | (alter-var-root #'system component/start) 429 | ;; Starting database 430 | ;; Opening database connection 431 | ;; Starting scheduler 432 | ;; Starting ExampleComponent 433 | ;; execute-query 434 | ;;=> #examples.ExampleSystem{ ... } 435 | 436 | (alter-var-root #'system component/stop) 437 | ;; Stopping ExampleComponent 438 | ;; Stopping scheduler 439 | ;; Stopping database 440 | ;; Closing database connection 441 | ;;=> #examples.ExampleSystem{ ... } 442 | ``` 443 | 444 | See the [reloaded] template for a more elaborate example. 445 | 446 | [reloaded]: https://github.com/stuartsierra/reloaded 447 | 448 | 449 | ### Web Applications 450 | 451 | Many Clojure web frameworks and tutorials are designed around an 452 | assumption that a "handler" function exists as a global `defn`, 453 | without any context. With this assumption, there is no easy way to use 454 | any application-level context in the handler without making it also a 455 | global `def`. 456 | 457 | The 'component' approach assumes that any "handler" function receives 458 | its state/context as an argument, without depending on any global state. 459 | 460 | To reconcile these two approaches, create the "handler" function as a 461 | *closure* over one or more components in a Lifecycle `start` method. 462 | Pass this closure to the web framework as the "handler". 463 | 464 | Most web frameworks or libraries that have a static `defroutes` or 465 | similar macro will provide an equivalent non-static `routes` which can 466 | be used to create a closure. 467 | 468 | It might look something like this: 469 | 470 | ```clojure 471 | (defn app-routes 472 | "Returns the web handler function as a closure over the 473 | application component." 474 | [app-component] 475 | ;; Instead of static 'defroutes': 476 | (web-framework/routes 477 | (GET "/" request (home-page app-component request)) 478 | (POST "/foo" request (foo-page app-component request)) 479 | (not-found "Not Found"))) 480 | 481 | (defrecord WebServer [http-server app-component] 482 | component/Lifecycle 483 | (start [this] 484 | (assoc this :http-server 485 | (web-framework/start-http-server 486 | (app-routes app-component)))) 487 | (stop [this] 488 | (stop-http-server http-server) 489 | this)) 490 | 491 | (defn web-server 492 | "Returns a new instance of the web server component which 493 | creates its handler dynamically." 494 | [] 495 | (component/using (map->WebServer {}) 496 | [:app-component])) 497 | ``` 498 | 499 | 500 | ## More Advanced Usage 501 | 502 | ### Errors 503 | 504 | While starting/stopping a system, if any component's `start` or `stop` 505 | method throws an exception, the `start-system` or `stop-system` 506 | function will catch and wrap it in an `ex-info` exception with the 507 | following keys in its `ex-data` map: 508 | 509 | * `:system` is the current system, including all the components which 510 | have already been started. 511 | 512 | * `:component` is the component which caused the exception, with its 513 | dependencies already `assoc`'d in. 514 | 515 | The original exception which the component threw is available as 516 | `.getCause` on the exception. 517 | 518 | The 'Component' library makes no attempt to recover from errors in a 519 | component, but you can use the `:system` attached to the exception to 520 | clean up any partially-constructed state. 521 | 522 | Since component maps may be large, with a lot of repetition, you 523 | probably don't want to log or print this exception as-is. The 524 | `ex-without-components` helper function will remove the larger objects 525 | from an exception. 526 | 527 | The `ex-component?` helper function tells you if an exception was 528 | originated or wrapped by 'Component'. 529 | 530 | 531 | ### Idempotence 532 | 533 | You may find it useful to define your `start` and `stop` methods to be 534 | idempotent, i.e., to have effect only if the component is not already 535 | started or stopped. 536 | 537 | ```clojure 538 | (defrecord IdempotentDatabaseExample [host port connection] 539 | component/Lifecycle 540 | (start [this] 541 | (if connection ; already started 542 | this 543 | (assoc this :connection (connect host port)))) 544 | (stop [this] 545 | (if (not connection) ; already stopped 546 | this 547 | (do (.close connection) 548 | (assoc this :connection nil))))) 549 | ``` 550 | 551 | The 'Component' library does not require that stop/start be 552 | idempotent, but idempotence can make it easier to clean up state after 553 | an error, because you can call `stop` indiscriminately on everything. 554 | 555 | In addition, you could wrap the body of `stop` in a try/catch that 556 | ignores all exceptions. That way, errors stopping one component will 557 | not prevent other components from shutting down cleanly. 558 | 559 | ```clojure 560 | (try (.close connection) 561 | (catch Throwable t 562 | (log/warn t "Error when stopping component"))) 563 | ``` 564 | 565 | 566 | ### Stateless Components 567 | 568 | The default implementation of `Lifecycle` is a no-op. If you omit the 569 | `Lifecycle` protocol from a component, it can still participate in the 570 | dependency injection process. 571 | 572 | Components which do not need a lifecycle can be ordinary Clojure maps. 573 | 574 | You **cannot** omit just one of the `start` or `stop` methods: any 575 | component which implements `Lifecycle` must supply both. 576 | 577 | 578 | ### Reloading 579 | 580 | I developed this pattern in combination with my "reloaded" [workflow]. 581 | For development, I might create a `user` namespace like this: 582 | 583 | [workflow]: http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded 584 | 585 | ```clojure 586 | (ns user 587 | (:require [com.stuartsierra.component :as component] 588 | [clojure.tools.namespace.repl :refer (refresh)] 589 | [examples :as app])) 590 | 591 | (def system nil) 592 | 593 | (defn init [] 594 | (alter-var-root #'system 595 | (constantly (app/example-system {:host "dbhost.com" :port 123})))) 596 | 597 | (defn start [] 598 | (alter-var-root #'system component/start)) 599 | 600 | (defn stop [] 601 | (alter-var-root #'system 602 | (fn [s] (when s (component/stop s))))) 603 | 604 | (defn go [] 605 | (init) 606 | (start)) 607 | 608 | (defn reset [] 609 | (stop) 610 | (refresh :after 'user/go)) 611 | ``` 612 | 613 | 614 | ### Subsystems 615 | 616 | What if you want to start just some of the components in your system? 617 | 618 | Systems are not designed to be in a "partially started" state, and the 619 | consequences are confusing. 620 | 621 | Instead, create a new system containing just the components you 622 | need. The `subsystem` function (added in 1.1.0) acts like Clojure's 623 | `select-keys` function, but for systems, and it automatically includes 624 | all transitive dependencies. 625 | 626 | Note: Although the `start-system` function takes an argument naming 627 | the components to be started, this is more of an implementation 628 | detail. It does **not** take dependencies into account. Use 629 | `subsystem` and `start` instead. 630 | 631 | 632 | ## Usage Notes 633 | 634 | 635 | ### Do not pass the system around 636 | 637 | The top-level "system" record is used only for starting and stopping 638 | other components, and for convenience during interactive development. 639 | 640 | See "Entry Points in ..." above. 641 | 642 | 643 | ### No function should take the entire system as an argument 644 | 645 | Application functions should never receive the whole system as an 646 | argument. This is unnecessary sharing of global state. 647 | 648 | Rather, each function should be defined in terms of **at most one** 649 | component. 650 | 651 | If a function depends on several components, then it should have its 652 | own component with dependencies on the things it needs. 653 | 654 | 655 | ### No component should be aware of the system which contains it 656 | 657 | Each component receives references only to the components on which it 658 | depends. 659 | 660 | 661 | ### Do not nest systems 662 | 663 | It's technically possible to nest one `system-map` in another, but the 664 | effects on dependencies are subtle and confusing. 665 | 666 | Instead, give all your components unique keys and merge them into one 667 | system. 668 | 669 | 670 | ### Other kinds of components 671 | 672 | The "application" or "business logic" may itself be represented by one 673 | or more components. 674 | 675 | Component records may, of course, implement other protocols besides 676 | `Lifecycle`. 677 | 678 | Any type of object, not just maps and records, can be a component if 679 | it has no lifecycle and no dependencies. For example, you could put a 680 | bare Atom or core.async Channel in the system map where other 681 | components can depend on it. 682 | 683 | 684 | ### Test doubles 685 | 686 | Different implementations of a component (for example, a stub version 687 | for testing) can be injected into a system with `assoc` before calling 688 | `start`. 689 | 690 | 691 | ### Notes for Library Authors 692 | 693 | 'Component' is intended as a tool for applications, not reusable 694 | libraries. I would not expect a general-purpose library to impose any 695 | particular framework on the applications which use it. 696 | 697 | That said, library authors can make it trivially easy for applications 698 | to use their libraries in combination with the 'Component' pattern by 699 | following these guidelines: 700 | 701 | * Never create global mutable state (for example, an Atom or Ref 702 | stored in a `def`). 703 | 704 | * Never rely on dynamic binding to convey state (for example, the 705 | "current" database connection) unless that state is necessarily 706 | confined to a single thread. 707 | 708 | * Never perform side effects at the top level of a source file. 709 | 710 | * Encapsulate all the runtime state needed by the library in a single 711 | data structure. 712 | 713 | * Provide functions to construct and destroy that data structure. 714 | 715 | * Take the encapsulated runtime state as an argument to any library 716 | functions which depend on it. 717 | 718 | 719 | ### Customization 720 | 721 | A system map is just a record that implements the Lifecycle protocol 722 | via two public functions, `start-system` and `stop-system`. These two 723 | functions are just special cases of two other functions, 724 | `update-system` and `update-system-reverse`. (Added in 0.2.0) 725 | 726 | You could, for example, define your own lifecycle functions as new 727 | protocols. You don't even have to use protocols and records; 728 | multimethods and ordinary maps would work as well. 729 | 730 | Both `update-system` and `update-system-reverse` take a function as 731 | an argument and call it on each component in the system. Along the 732 | way, they `assoc` in the updated dependencies of each component. 733 | 734 | The `update-system` function iterates over the components in 735 | dependency order: a component will be called *after* its dependencies. 736 | The `update-system-reverse` function goes in reverse dependency order: 737 | a component will be called *before* its dependencies. 738 | 739 | Calling `update-system` with the `identity` function is equivalent to 740 | doing just the dependency injection part of 'Component' without 741 | `Lifecycle`. 742 | 743 | 744 | 745 | ## References / More Information 746 | 747 | * [video from Clojure/West 2014](https://www.youtube.com/watch?v=13cmHf_kt-Q) (YouTube, 40 minutes) 748 | * [tools.namespace](https://github.com/clojure/tools.namespace) 749 | * [On the Perils of Dynamic Scope](http://stuartsierra.com/2013/03/29/perils-of-dynamic-scope) 750 | * [Clojure in the Large](http://www.infoq.com/presentations/Clojure-Large-scale-patterns-techniques) (video) 751 | * [Relevance Podcast Episode 32](http://thinkrelevance.com/blog/2013/05/29/stuart-sierra-episode-032) (audio) 752 | * [My Clojure Workflow, Reloaded](http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded) 753 | * [reloaded](https://github.com/stuartsierra/reloaded) Leiningen template 754 | * [Lifecycle Composition](http://stuartsierra.com/2013/09/15/lifecycle-composition) 755 | 756 | 757 | 758 | ## Copyright and License 759 | 760 | The MIT License (MIT) 761 | 762 | Copyright © 2015 Stuart Sierra 763 | 764 | Permission is hereby granted, free of charge, to any person obtaining a copy of 765 | this software and associated documentation files (the "Software"), to deal in 766 | the Software without restriction, including without limitation the rights to 767 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 768 | the Software, and to permit persons to whom the Software is furnished to do so, 769 | subject to the following conditions: 770 | 771 | The above copyright notice and this permission notice shall be included in all 772 | copies or substantial portions of the Software. 773 | 774 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 775 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 776 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 777 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 778 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 779 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 780 | --------------------------------------------------------------------------------