and a bit of Stuart Sierra's reloaded workflow
Karol Andrusieczko
Clojure Meetup, 26.08.2015
Digital advertising industry. Rapidly growing startup.
The global leader for local audience data with 1.3 billion unique profiles in Europe, APAC and the Americas.
Our partners are:
- advertisers
- publishers
- data suppliers
Cassandra
ElasticSearch
Postgres (for legacy)
Ring (Immutant)
Compojure
Liberator
Manipulating syntax, not text
Two important things:
clojure.tools.namespace.repl
NO GLOBAL STATE
The idea
To be able to shut the application down, discard any transient state it might have built up, start it again, and return to a similar state (...) in less than a second.
How?
What's a global state?
(def state-a (ref {}))
(def state-b (atom 0))
; hidden dependencies coming from outside
(defn op1 []
(dosync
(alter state-a ...)))
(defn op2 []
(dosync
(swap! state-b ...)))
(let [state-a (ref {})
state-b (atom 0)]
(defn op1 []
(dosync
(alter state-a ...)))
(defn op2 []
(dosync
(swap! state-b ...))))
How to avoid global state?
(defn constructor []
{:a (ref {})
:b (atom 0)})
; makes function dependencies clear
(defn op1 [state]
(dosync
(alter (:a state) ...)))
(defn op2 [state]
(dosync
(swap! (:b state) ...)))
Really easy to test!
(defn constructor []
{:a (ref {:initial "awesome test!"})
:b (atom 0)})
(deftest test-op1
(let [state (constructor)]
(is (= ... (op1 state)))))
and reload! :)
Also wrong - global state
(def ^:dynamic *resource*)
(defn- internal-op1 []
... *resource* ...)
(defn op1 [arg]
(internal-op1 ...))
assumptions:
But... but... what about my db connection?!
(defn init! []
(connect-to-database!)
(create-thread-pools!)
(start-background-processes!)
(start-web-server!))
Constructor
;; In src/com/example/my_project/system.clj
(ns com.example.my-project.system)
(defn system
"Returns a new instance of the whole application."
[]
...)
{:db {:uri "datomic:mem://dev"}
:scheduler #<ScheduledThreadPoolExecutorService ...>
:cache #<Atom {}>
:handler #<Fn ...>
:server #<Jetty ...>}
Different constructors
(defrecord System
[storage config web-service])
(defn dev-system []
(->System (mem-store)
(local-config {:a 1 :b 2})
(mock-web-service)))
(defn prod-system []
(let [config (zookeeper-config)
storage (sql-store config)
service (web-srv config storage)]
(->System storage config service)))
start and stop
;; In src/com/example/my_project/system.clj
(defn start
"Performs side effects to initialize the system, acquire resources,
and start it running. Returns an updated instance of the system."
[system]
...)
(defn stop
"Performs side effects to shut down the system and release its
resources. Returns an updated instance of the system."
[system]
...)
dev/user.clj and demo