Skip to content

Commit ab02501

Browse files
author
Stuart Sierra
committed
Initial commit
0 parents  commit ab02501

File tree

7 files changed

+447
-0
lines changed

7 files changed

+447
-0
lines changed

.gitignore

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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

LICENSE.txt

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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.

README.md

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# component
2+
3+
'Component' is a tiny Clojure framework for managing the lifecycle of
4+
software components with runtime state which must be manually
5+
initialized and destroyed.
6+
7+
This is primarily a design pattern with a few helper functions.
8+
9+
10+
11+
## Releases and Dependency Information
12+
13+
No releases yet. Run `lein install` in this directory to install the
14+
current SNAPSHOT version.
15+
16+
* Releases will be published to [Clojars]
17+
18+
* Latest stable release is TODO_LINK
19+
20+
* All released versions TODO_LINK
21+
22+
[Leiningen] dependency information:
23+
24+
[com.stuartsierra/component "0.1.0-SNAPSHOT"]
25+
26+
[Maven] dependency information:
27+
28+
<dependency>
29+
<groupId>com.stuartsierra/groupId>
30+
<artifactId>component</artifactId>
31+
<version>0.1.0-SNAPSHOT</version>
32+
</dependency>
33+
34+
[Clojars]: http://clojars.org/
35+
[Leiningen]: http://leiningen.org/
36+
[Maven]: http://maven.apache.org/
37+
38+
39+
40+
## Introduction
41+
42+
For the purposes of this framework, a *component* is a collection of
43+
functions or procedures which share some runtime state.
44+
45+
Some examples of components:
46+
47+
* Database access: query and insert functions sharing a database
48+
connection
49+
50+
* External API service: functions to send and retrieve data sharing an
51+
HTTP connection pool
52+
53+
* Web server: functions to handle different routes sharing all the
54+
runtime state of the web application, such as a session store
55+
56+
* In-memory cache: functions to get and set data in a shared mutable
57+
reference such as a Clojure Atom or Ref
58+
59+
A *component* is similar in spirit to the definition of an *object* in
60+
Object-Oriented Programming.
61+
62+
Clojure is not an object-oriented programming language, so we do not
63+
have to cram everything into this model. Some functions are just
64+
functions. But real-world applications need to manage state.
65+
Components are a tool to help with that.
66+
67+
68+
69+
## Usage
70+
71+
See file `dev/examples.clj`
72+
73+
74+
75+
76+
## Change Log
77+
78+
* Version 0.1.0-SNAPSHOT
79+
80+
81+
82+
## Copyright and License
83+
84+
The MIT License (MIT)
85+
86+
Copyright © 2013 Stuart Sierra
87+
88+
Permission is hereby granted, free of charge, to any person obtaining a copy of
89+
this software and associated documentation files (the "Software"), to deal in
90+
the Software without restriction, including without limitation the rights to
91+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
92+
the Software, and to permit persons to whom the Software is furnished to do so,
93+
subject to the following conditions:
94+
95+
The above copyright notice and this permission notice shall be included in all
96+
copies or substantial portions of the Software.
97+
98+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
99+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
100+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
101+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
102+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
103+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

dev/examples.clj

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
(prn "Opening database connection")
8+
(reify java.io.Closeable
9+
(close [_] (prn "Closing database connection"))))
10+
11+
(defn execute-query [& _]
12+
(prn 'execute-query))
13+
14+
(defn execute-insert [& _]
15+
(prn 'execute-insert))
16+
17+
(defn new-scheduler []
18+
(prn 'new-scheduler)
19+
{})
20+
21+
22+
;;; Example database component
23+
24+
;; To define a component, define a Clojure record that implements the
25+
;; `Lifecycle` protocol.
26+
27+
(defrecord Database [host port connection]
28+
;; Implement the Lifecycle protocol
29+
component/Lifecycle
30+
31+
(start [component]
32+
;; In the 'start' method, initialize this component
33+
;; and start it running. For example, connect to a
34+
;; database, create thread pools, or initialize shared
35+
;; state.
36+
(let [conn (connect-to-database host port)]
37+
;; Return an updated version of the component with
38+
;; the run-time state assoc'd in.
39+
(assoc component :connection conn)))
40+
41+
(stop [component]
42+
;; In the 'stop' method, shut down the running
43+
;; component and release any external resources it has
44+
;; acquired.
45+
(.close connection)
46+
;; Return the component, optionally modified.
47+
component))
48+
49+
;; Optionally, provide a constructor function that takes in
50+
;; the essential configuration parameters of the component,
51+
;; leaving the runtime state blank.
52+
53+
(defn new-database [host port]
54+
(map->Database {:host host :port port}))
55+
56+
;; Define the functions implementing the behavior of the
57+
;; component to take the component itself as an argument.
58+
59+
(defn get-user [database username]
60+
(execute-query (:connection database)
61+
"SELECT * FROM users WHERE username = ?"
62+
username))
63+
64+
(defn add-user [database username favorite-color]
65+
(execute-insert (:connection database)
66+
"INSERT INTO users (username, favorite_color)"
67+
username favorite-color))
68+
69+
70+
;;; Second Example Component
71+
72+
;; Define other components in terms of the components on which they
73+
;; depend.
74+
75+
(defrecord ExampleComponent [options cache database scheduler]
76+
component/Lifecycle
77+
78+
(start [this]
79+
;; In the 'start' method, a component may assume that its
80+
;; dependencies are available and have already been started.
81+
(assoc this :admin (get-user database "admin")))
82+
83+
(stop [this]
84+
;; Likewise, in the 'stop' method, a component may assume that its
85+
;; dependencies will not be stopped until AFTER it is stopped.
86+
this))
87+
88+
;; Not all the dependencies need to be supplied at construction time.
89+
;; In general, the constructor should not depend on other components
90+
;; being available or started.
91+
92+
(defn example-component [config-options]
93+
(map->ExampleComponent {:options config-options
94+
:cache (atom {})}))
95+
96+
97+
;;; Example System
98+
99+
;; Components are composed into systems. A system is a component which
100+
;; knows how to start and stop other components.
101+
102+
;; A system can use the helper functions `start-system` and `stop-system`,
103+
;; which take a set of keys naming components in the system to be
104+
;; started/stopped. Order of the keys doesn't matter here.
105+
106+
(defrecord ExampleSystem [config-options db scheduler app]
107+
component/Lifecycle
108+
(start [this]
109+
(component/start-system this [:scheduler :app :db]))
110+
(stop [this]
111+
(component/stop-system this [:scheduler :app :db])))
112+
113+
;; When constructing the system, specify the dependency relationships
114+
;; among components using the `using` function.
115+
116+
(defn example-system [config-options]
117+
(let [{:keys [host port]} config-options]
118+
(map->ExampleSystem
119+
{:config-options config-options
120+
:db (new-database host port)
121+
:scheduler (new-scheduler)
122+
:app (component/using
123+
(example-component config-options)
124+
{:database :db
125+
:scheduler :scheduler})})))
126+
;; ^ ^
127+
;; | |
128+
;; | \- Keys in the ExampleSystem record
129+
;; |
130+
;; \- Keys in the ExampleComponent record
131+
132+
;; `using` takes a component and a map telling the system where to
133+
;; find that component's dependencies. Keys in the map are the keys in
134+
;; the component record itself, values are the map are the
135+
;; corresponding keys in the system record.
136+
137+
;; Based on this information (stored as metadata on the component
138+
;; records) the `start-system` function will construct a dependency
139+
;; graph of the components, assoc in their dependencies, and start
140+
;; them all in the correct order.
141+
142+
;; Optionally, if the keys in the system map are the same as in the
143+
;; component map, they may be passed as a vector to `using`. If you
144+
;; know all the dependencies in advance, you may even add the metadata
145+
;; in the component's constructor:
146+
147+
(defrecord AnotherComponent [component-a component-b])
148+
149+
(defn another-component []
150+
(component/using
151+
(->AnotherComponent nil nil)
152+
[:component-a :component-b]))
153+
154+
(defrecord AnotherSystem [component-a component-b component-c])
155+
156+
157+
158+
159+
;; Local Variables:
160+
;; clojure-defun-style-default-indent: t
161+
;; End:

dev/user.clj

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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}))

project.clj

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(defproject com.stuartsierra/component "0.1.0-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 [[org.clojure/clojure "1.5.1"]
7+
[com.stuartsierra/dependency "0.1.1"]]
8+
:profiles {:dev {:dependencies [[org.clojure/tools.namespace "0.2.4"]]
9+
:source-paths ["dev"]}})

0 commit comments

Comments
 (0)