Я пишу приложение на основе Compojure и не могу понять, как обрабатывать состояние очистки при перезагрузке кода. В частности, мое приложение имеет открытый дескриптор базы данных LevelDB, и его необходимо либо повторно использовать, либо правильно закрыть, прежде чем он будет снова открыт.
Я просмотрел различные предложения о том, как работать с состоянием в приложениях Compojure, и нашел два шаблона. Первый, который можно увидеть в этом вопросе SO ("Использование LevelDB в Ring Compojure webapp") предлагает использовать глобальную переменную, ссылающуюся на атом/ссылку/задержку (я видел несколько разных предложений). Это решение не кажется мне идеальным, это по существу глобальное изменяемое состояние, и похоже, что оно настоятельно не рекомендуется (например, в эту презентацию). Также неясно, работает ли это с перезагрузкой кода (может быть, с defonce
вместо def
?)
Второй шаблон заключается в том, чтобы вводить состояние с помощью промежуточного программного обеспечения, как в SO-вопрос («Передача состояния в качестве параметра кольцевому обработчику»). Этот паттерн выглядит намного лучше, поскольку он позволяет избежать изменяемого состояния, но он не работает с перезагрузкой кода. Вот мой код, который использует этот шаблон:
(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, не прибегая к глобальному изменяемому состоянию? Я видел презентацию Стюарта Сьерры "Clojure in the Large", и в ней есть несколько хороших предложений, но они кажутся слишком слишком сложно для такого крошечного приложения, как мое.
- Есть ли способ зарегистрироваться, чтобы получить обратный вызов, когда код собирается перезагрузиться, чтобы я мог закрыть дескриптор открытой базы данных?