【发布时间】:2014-07-21 15:31:41
【问题描述】:
我正在编写一个基于 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-state 和add-app-state 之间的划分只是为了更容易在测试中重用add-app-state,而不会将其与打开数据库的代码结合。
当使用lein ring server-headless 运行此代码时,它工作正常,直到我编辑代码,然后它在下一个请求时爆炸,因为app 已重新创建,create-initial-state 已再次运行。不再引用旧数据库句柄,但由于 LevelDB 保留锁定文件以确保只有一个进程在使用数据库,并且我的代码从不关闭句柄,因此打开新句柄是错误的。
- 当代码在 Ring 应用程序中重新加载时,人们通常如何处理诸如数据库连接、打开文件和套接字之类的事情,而不求助于全局可变状态?我见过Stuart Sierra's "Clojure in the Large" presentation,它提供了一些很好的建议,但对于像我这样的小应用程序来说,它们感觉很复杂。
- 有没有办法注册在代码即将重新加载时获取回调,以便我可以关闭打开的数据库句柄?
【问题讨论】:
标签: clojure state ring compojure