简介
Component用于管理拥有运行期状态的组件的生命周期和依赖关系。
创建组件
一个实现了Lifecycle协议的record就是一个组件。
(defrecord Database [host port connection]
;; 实现Lifecycle协议
component/Lifecycle
(start [component]
(println ";; Starting database")
;; 在start方法初始化组件并启动。例如,连接到数据库,创建线程池等。
(let [conn (connect-to-database host port)]
;; 返回更新后的组件,附带了运行时状态。
(assoc component :connection conn)))
(stop [component]
(println ";; Stopping database")
;; 在stop方法停止组件,并释放组件所获取的外部资源。
(.close connection)
;; 返回组件,修改组件的状态是可选的。
(assoc component :connection nil)))
可以定义一个创建函数初始化基本配置,保留运行期配置为空。
(defn new-database [host port]
(map->Database {:host host :port port}))
使用组件
需要使用组件的函数,传入一个组件实例作为参数。
(defn get-user [database username]
(execute-query (:connection database)
"SELECT * FROM users WHERE username = ?"
username))
组件依赖
如果a组件依赖于b组件,在a组件中定义一个属性用于存放所依赖的b组件。Component
库自动注入
所依赖的组件。
不要在构造函数中传入依赖的组件。
(defrecord ExampleComponent [options cache database scheduler]
component/Lifecycle
(start [this]
(println ";; Starting ExampleComponent")
;; 在start方法中,该组件的依赖组件都已经启动完成,并且注入进来了,可以直接使用。
(assoc this :admin (get-user database "admin")))
(stop [this]
(println ";; Stopping ExampleComponent")
;; 在stop方法中,该组件的依赖组件仍在启动状态,直到当前组件停止后,依赖组件才会停止。
this))
System
System用于启动和停止其他组件,以及负责注入组件依赖。创建system最简单的方式是使用system-map函数,传入一组key和组件实例作为参数。使用using函数指定依赖关系。
(component/system-map
:db (new-database host port)
:scheduler (new-scheduler)
:app (component/using
(example-component config-options)
{:database :db
:scheduler :scheduler}))
如果键名相同,可以使用vector指定依赖关系。
(component/system-map
:database (new-database host port)
:scheduler (new-scheduler)
:app (component/using
(example-component config-options)
[:database :scheduler]))
system本身也是组件,system-map输出的system实例同样实现了Lifecycle协议,并负责其他组件的启动和停止。
可以使用system-using一次性指定整个system的依赖关系。
(-> (component/system-map
:config-options config-options
:db (new-database host port)
:sched (new-scheduler)
:app (example-component config-options))
(component/system-using
{:app {:database :db
:scheduler :sched}}))
Reload
参考这个工作流。在开发环境中:
(ns user
(:require [com.stuartsierra.component :as component]
[clojure.tools.namespace.repl :refer (refresh)]
[examples :as app]))
(def system nil)
(defn init []
(alter-var-root #'system
(constantly (app/example-system {:host "dbhost.com" :port 123}))))
(defn start []
(alter-var-root #'system component/start))
(defn stop []
(alter-var-root #'system
(fn [s] (when s (component/stop s)))))
(defn go []
(init)
(start))
(defn reset []
(stop)
(refresh :after 'user/go))
生产环境
(ns com.example.application.main
(:gen-class)
(:require [com.stuartsierra.component :as component]
[examples :as app]))
(defn -main [& args]
(let [[host port] args]
(component/start
(app/example-system {:host host :port port}))))