Component library

Post on 13-Aug-2015

193 views 1 download

Tags:

Transcript of 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