Download - Component library
component libraryhttps://github.com/stuartsierra/component
https://github.com/jordillonch/component-example
@jordillonch July 2015
Agenda
• component library introduction
• example
Introduction
Tiny Clojure framework for managing the lifecycle of
software components which have runtime state
Real-world applications need to manage state
It can be seen as a style of dependency injection using
immutable data structures
Components and
Systems
Components
A component is a collection of functions which share
some runtime state
A component is similar in spirit to the definition of an object in Object-Oriented Programming
A component knows how to start and stop the application pieces
that have to manage state
Some examples
• Database access: database connection
• External API service: HTTP connection pool
• Web server: a session store
• In-memory cache: a Clojure Atom or Ref
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
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
Each component receives references only to the things it needs, avoiding unnecessary
shared state
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 all state reachable via a single "system" object makes it easy to reach in and inspect any part of the application from
the REPL
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
It is not easy to retrofit the component model to an existing
application without major refactoring
For small applications, declaring the dependency relationships among
components may actually be more work than manually starting all the components
in the correct order
The system map is too large to inspect visually
The code cannot discover relationships
automatically
Cyclic dependencies are forbidden among
components
Some code
(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)))
(defn new-database [host port] (map->Database {:host host :port port}))
(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))
(defn example-component [config-options] (map->ExampleComponent {:options config-options :cache (atom {})}))
(defn example-system [config-options] (let [{:keys [host port]} config-options] (component/system-map :database (new-database host port) :scheduler (new-scheduler) :app (component/using (example-component config-options) [:database :scheduler]))))
example-system
app
ExampleComponent
database
Database
scheduler
Scheduler
(go)
example-systemexample-system
app
ExampleComponent
database
Database
scheduler
Scheduler
app
ExampleComponent
database
Database
scheduler
Scheduler
(reset)
Our examplehttps://github.com/jordillonch/component-example
Shows how component library works and how use a workflow that let you change code and
avoid the JVM restarting
The example is just an API that exposes one
endpoint to do additions
curl http://localhost:8080/math/sum -v --data "value1=1;value2=2"
my-system
application-api
ApplicationApiComponent
context-math-engine
MathOracleSimple
(defn my-system []
(component/system-map
:context-math-engine (new-context-math-engine-system)
:application-api (component/using (new-application-api) [:context-math-engine])))
Demo time :)
Resources
https://github.com/danielsz/system
https://github.com/stuartsierra/component
https://youtu.be/13cmHf_kt-Q
http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded
Thanks