18

What is an idiomatic way to handle application configuration in clojure?

So far I use this environment:

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

Which has many downside:

  • The path to config.clj might not always be resolved correctly
  • No clear way to structure config sections for used libraries/frameworks
  • Not globally accessible (@app/config) (which of course, can be seen as a good functional style way, but makes access to config across source file tedious.

Bigger open-source projects like storm seem to use YAML instead of Clojure and make the config accessible globally via a bit ugly hack: (eval ``(def ~(symbol new-name) (. Config ~(symbol name)))).

4

3 回答 3

12

首先使用 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)))

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

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

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

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

于 2013-07-20T15:24:48.430 回答
1

我什至不会将配置映射作为资源保存在单独的文件中(对于每个环境)。Confijulate(https://github.com/bbbates/confijulate,是的 - 这是一个个人项目)允许您在单个命名空间中为每个环境定义所有配置,并通过系统属性在它们之间切换。但是,如果您需要在不重建的情况下即时更改值,Confijulate 也允许您这样做。

于 2014-01-01T23:45:13.573 回答
1

在过去的一个月里,我做了很多这样的工作。对于不接受传递配置的情况,我们在原子中使用了全局配置映射。在应用程序启动的早期,config varswap!与加载的配置一起编辑,之后它就被单独放置。这在实践中有效,因为它在应用程序的生命周期内实际上是不可变的。不过,这种方法可能不适用于图书馆。

我不确定您所说的“没有明确的方法来构造已用库/框架的配置部分”是什么意思。您希望库可以访问配置吗?无论如何,我创建了一个配置加载器管道,该管道被赋予在启动时设置配置的函数。这允许我根据库和源分离配置。

于 2013-07-18T14:57:02.480 回答