运行Ring和Compojure时清除代码重新加载的状态

时间:2014-06-01 10:26:03

标签: clojure state ring compojure

我正在编写一个基于Compojure的应用程序,并且在重新加载代码时无法解决如何处理清理状态的问题。具体来说,我的应用程序有一个打开LevelDB数据库的句柄,这需要在再次打开之前重新使用或关闭。

我已经四处寻找有关如何在Compojure应用程序中处理状态的不同建议,并找到了两种模式。可以在此SO question ("Using LevelDB in a Ring Compojure webapp")中看到的第一个建议使用全局变量引用原子/参考/延迟(我已经看到了一些不同的建议)。这个解决方案对我来说似乎并不理想,它本质上是全局可变状态,看起来强烈不鼓励它(例如在this presentation中)。它还不清楚它是否适用于代码重新加载(可能使用defonce代替def?)

第二种模式是使用一个中间件注入状态,如this SO question ("Passing state as parameter to a ring handler")。这种模式看起来好多了,因为它避免了可变状态,但它不能用于代码重新加载。这是我使用该模式的代码:

(defn create-initial-state []
  {:db (delay (storage/create "data.leveldb"))})

(defn add-app-state [handler state]
  (fn [request]
    (handler (assoc request :app-state state))))

(defroutes app-routes
  (GET "/signals" request
    (handle-signals (:app-state request))))

(def app
  (-> app-routes
      (add-app-state (create-initial-state))
      (handler/site)))

handle-signals的实施并不重要,所以我将其排除在外。 storage/create打开LevelDB数据库并返回句柄。数据库包含在delay中,以避免在我的测试中加载代码时创建它。 create-initial-stateadd-app-state之间的划分只是为了更容易在测试中重用add-app-state而不将其与打开数据库的代码相结合。

当此代码与lein ring server-headless一起运行时,它可以正常运行,直到我编辑代码,然后在重新创建app后,它会在下一个请求中爆炸,并且create-initial-state再次运行。不再引用旧的数据库句柄,但由于LevelDB保持锁定文件以确保只有一个进程使用数据库,并且我的代码永远不会关闭句柄,因此打开新句柄是一个错误。

  • 当在Ring应用程序中重新加载代码时,人们通常如何处理数据库连接,打开文件和套接字等事情,而不诉诸全局可变状态?我已经看过Stuart Sierra's "Clojure in the Large" presentation并提供了一些很好的建议,但他们觉得像我这样的小应用程序很复杂。
  • 是否有办法在代码即将重新加载时注册以获取回调,以便我可以关闭打开的数据库句柄?

1 个答案:

答案 0 :(得分:1)

尽量不要将def用于非静态的东西。看看Component。组件甚至可用于启动Jetty(或类似)与您的应用程序并处理卸载/重新加载。