Очистка состояния при перезагрузке кода при запуске Ring и Compojure

Я пишу приложение на основе 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", и в ней есть несколько хороших предложений, но они кажутся слишком слишком сложно для такого крошечного приложения, как мое.
  • Есть ли способ зарегистрироваться, чтобы получить обратный вызов, когда код собирается перезагрузиться, чтобы я мог закрыть дескриптор открытой базы данных?

person Theo    schedule 01.06.2014    source источник


Ответы (1)


Попробуйте не использовать def для того, что не статично. Взгляните на Компонент. Компонент даже можно использовать для запуска Jetty (или аналогичного) с вашим приложением и обработки выгрузки/перезагрузки.

person Michael Klishin    schedule 01.06.2014
comment
Похоже, Стюарт Сьерра взял идеи из презентации, на которую я ссылался, и сделал из них основу, я попробую. Я надеялся, что для небольших приложений, таких как мое, есть менее сложное решение, но, возможно, компонент не сложен, когда вы с ним работаете. Спасибо. - person Theo; 01.06.2014