在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)]
    ...))


有很多缺点:


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


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

最佳答案

首先使用clojure.edn,尤其是clojure.edn / read。例如

(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)))


关于使用io / resource的config.edn的路径只是处理此问题的一种方法。由于您可能希望在运行时保存更改后的config.edn,因此您可能希望依赖以下事实:使用非限定文件名构造的文件读取器和写入器的路径,例如

(io/reader "where-am-i.edn")


默认为

(System/getProperty "user.dir")


考虑到您可能想在运行时更改配置这一事实,可以实现这样的模式
(草图)

;; 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)))


基本上,这段代码包装了一个非全局原子,并提供了设置并可以访问它。您可以读取其当前状态,并使用sth像原子一样对其进行更改。就像(alter-userconfig! assoc :k2 3)。对于全局测试,可以重置! userconfig,还将各种userconfig注入到应用程序(alter-userconfig! (constantly {:k1 300, :k2 212}))中。

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

同样,上述让我们在userconfig上包装了一个监视,该监视在每次更改userconfig时都会自动更新.edn文件。如果您不想这样做,则可以添加save-userconfig!将原子内容吐入config.edn的函数。但是,您可能想创建一种向原子添加更多手表的方法(例如,在更改自定义字体大小后重新渲染GUI),我认为这会破坏上述模式的模样。

相反,如果您要处理更大的应用程序,则更好的方法是为userconfig定义一个协议(具有与let块类似的功能),并使用文件,数据库,原子(或任何您喜欢的东西)的各种构造函数来实现该协议。需要使用reify或defrecord测试/不同的使用场景)。可以在应用程序中传递它的一个实例,每个状态操作/ io函数都应使用它而不是全局变量。

关于configuration - Clojure中的惯用配置管理?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/17719976/

10-10 06:27