我正在编写一个基于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保持锁定文件以确保只有一个进程使用数据库,并且我的代码永远不会关闭句柄,因此打开新句柄是一个错误。