Configuration clojure中惯用的配置管理?

Configuration clojure中惯用的配置管理?,configuration,clojure,configure,configuration-management,idioms,Configuration,Clojure,Configure,Configuration Management,Idioms,在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)] ..

在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
    )(当然,这可以看作是一种很好的功能风格方式,但会使跨源文件访问配置变得单调乏味

像storm这样更大的开源项目似乎使用YAML而不是Clojure,并通过一个有点难看的hack(eval```(def~(symbol new name)(.config~(symbol name)))使配置全局可访问.

在过去一个月的工作中,我已经做了相当多的工作。对于不允许传递配置的情况,我们在atom中使用了全局配置映射。在应用程序启动的早期,配置变量是
交换!
与加载的配置一起交换的,之后它就被单独保留了。这在实践中是有效的,因为在应用程序的生命周期中,它实际上是不可变的。不过,这种方法可能对库不起作用


我不知道你所说的“没有清晰的方法来为使用过的库/框架构造配置部分”是什么意思。你想让库访问配置吗?不管怎样,我创建了一个配置加载程序管道,该管道提供给在启动时设置配置的函数。这允许我根据库和源分离配置。

首先使用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)))
关于config.edn的路径,使用io/resource只是解决此问题的一种方法。由于您可能希望在运行时保存修改过的config.edn,因此您可能希望依赖这样一个事实,即文件读取器和写入器的路径是使用非限定文件名构造的,如

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

(System/getProperty "user.dir")
考虑到您可能希望在运行时更改配置,您可以实现如下模式 (草图)

基本上,这段代码包装了一个非全局的原子,并提供set和get访问它的权限。你可以读取它的当前状态,然后像原子一样用
(alter userconfig!assoc:k2 3)
之类的东西来修改它。对于全局测试,你可以重置userconfig,还可以将各种userconfig注入你的应用程序
(alter userconfig!(不断地{:k1300,:k2 212}))

需要userconfig的函数可以编写为 (拒绝做某事[cfg arg1 arg2 arg3] ...) 并使用各种配置进行测试,如默认用户配置、testconfig1、2、3。。。 像在用户面板中一样操纵userconfig的函数将使用get/alter..!函数

此外,上面还允许在userconfig上包装一个手表,每次更改userconfig时自动更新.edn文件。如果您不想这样做,可以添加一个save userconfig!函数,将atoms内容放入config.edn。但是,您可能需要创建一种向atom添加更多手表的方法(比如在自定义字体大小改变后重新呈现GUI),我认为这将打破上述模式的模式


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

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

考虑使用
clojure.edn/read string
而不是绑定
*read eval*
。关于解决问题时,请使用
(slurp(io/resource“config.clj))
当您的代码成为一个在jar中嵌入了配置的库时,这也会有所帮助
这种方法可能对库不起作用,尽管
-或者测试(这里是体验之声…)实际上,它可以很好地用于测试。在内部,一旦设置好配置,它就被视为不可变的。在这一点上,一切本质上都是一个常量全局。当我们进行测试时,我们只加载一个带有模拟值的配置。虽然,我们的配置主要是URL、凭据和不变的名称。我还没有看到任何使用con的实例fig可以微调逻辑等等。但是如果您打算在应用程序运行时更改配置,那么您是对的。在这种情况下,测试是复杂的。配置映射将无法以这种方式从其他线程访问。绑定是线程本地的。我想我的意思是使用atom和
交换!
中的配置映射。
;; 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)))