clojure中的惯用配置管理?

Kre*_*tur 16 configuration idiomatic clojure configure configuration-management

在clojure中处理应用程序配置的惯用方法是什么?

到目前为止我使用这个环境:

;; config.clj
{:k1 "v1"
 :k2 2}

;; core.clj
(defn config []
  (let [content (slurp "config.clj")]
    (binding [*read-eval* false]
      (read-string content))))

(defn -main []
  (let [config (config)]
    ...))
Run Code Online (Sandbox Code Playgroud)

这有很多缺点:

  • config.clj可能无法始终正确解析路径
  • 没有明确的方法来构建已使用的库/框架的配置节
  • 不是全局可访问的(@app/config)(当然,这可以看作是一种很好的功能样式方式,但是使得跨源文件的配置访问变得乏味.

像storm这样的大型开源项目似乎使用YAML而不是Clojure,并通过一些丑陋的hack使配置可以访问全局: (eval ``(def ~(symbol new-name) (. Config ~(symbol name)))).

Leo*_*hin 11

首先使用clojure.edn,特别是clojure.edn/read.E. g.

(use '(clojure.java [io :as io]))
(defn from-edn
  [fname]    
  (with-open [rdr (-> (io/resource fname)
                      io/reader
                      java.io.PushbackReader.)]
    (clojure.edn/read rdr)))
Run Code Online (Sandbox Code Playgroud)

关于使用io/resource的config.edn的路径只是解决这个问题的一种方法.由于您可能希望在运行时保存更改的config.edn,因此您可能希望依赖于使用非限定文件名构造的文件读取器和编写器的路径这样的事实

(io/reader "where-am-i.edn")
Run Code Online (Sandbox Code Playgroud)

默认为

(System/getProperty "user.dir")
Run Code Online (Sandbox Code Playgroud)

考虑到您可能希望在运行时更改配置,您可以实现这样的模式(粗略草图)

;; myapp.userconfig
(def default-config {:k1 "v1"
                     :k2 2})
(def save-config (partial spit "config.edn"))
(def load-config #(from-edn "config.edn")) ;; see from-edn above

(let [cfg-state (atom (load-config))]
  (add-watch cfg-state :cfg-state-watch
    (fn [_ _ _ new-state]
      (save-config new-state)))
  (def get-userconfig #(deref cfg-state))
  (def alter-userconfig! (partial swap! cfg-state))
  (def reset-userconfig! #(reset! cfg-state default-config)))
Run Code Online (Sandbox Code Playgroud)

基本上,这段代码包装了一个非全局的原子,并提供了set和get访问权限.你可以读取它的当前状态并像原子一样改变它.喜欢(alter-userconfig! assoc :k2 3).对于全球测试,您可以重置!userconfig并将各种用户配置注入您的应用程序(alter-userconfig! (constantly {:k1 300, :k2 212})).

需要userconfig的函数可以写成(defn do-sth [cfg arg1 arg2 arg3] ...)并使用各种配置进行测试,例如default-userconfig,testconfig1,2,3 ......操作userconfig的函数就像在用户面板将使用get/alter ..!功能.

此外,上面包含了一个watchconfig,用于每次更改userconfig时自动更新.edn文件的userconfig.如果您不想这样做,可以添加save-userconfig!将原子内容吐入config.edn的函数.但是,您可能想要创建一种向原子添加更多手表的方法(比如在更改自定义字体大小后重新渲染GUI),在我看来这会打破上述模式的模式.

相反,如果您正在处理更大的应用程序,更好的方法是为userconfig定义一个协议(具有类似于let块的类似函数),并使用各种构造函数为文件,数据库,原子(或任何其他人)实现它需要测试/不同的使用场景)利用reify或defrecord.这个实例可以在应用程序中传递,每个state-manipulationulating/io函数都应该使用它而不是全局的.