Как избежать глобального состояния в clojure при использовании «wrap-reload»? - PullRequest
0 голосов
/ 06 января 2019

Я изучаю clojure и столкнулся с трудностями при попытке реорганизовать мое веб-приложение, чтобы сделать его более функциональным и менее зависимым от глобального состояния.

То, как вы делаете простой сервер с автоматической перезагрузкой с ring, выглядит следующим образом:

(defn handler [req]
  {:status 200
   :body "<h1>Hello World</h1>"
   :headers {}})

(defn -main []
  (jetty/run-jetty (wrap-reload #'handler)
     {:port 12345}))

(источник: https://practicalli.github.io/clojure-webapps/middleware-in-ring/wrap-reload.html)

Итак, handler - это глобальная функция. Это дано как var ссылка на wrap-reload. При каждом запросе wrap-reload будет перезагружать все пространство имен, а затем повторно разрешать handler ссылку на потенциально другую функцию. Или, по крайней мере, это мое понимание.

Это все хорошо в этом простом примере. Проблема начинается, когда я заменяю этот hello world обработчик своим фактическим обработчиком, к которому привязаны все виды состояний (например, соединение с базой данных). Это состояние инициализируется один раз внутри -main, а затем вводится в стек маршрутизации. Примерно так:

(defn init-state [args dev]
  (let
   [db (nth args 1 "jdbc:postgresql://localhost/mydb")
    routes (make-routes dev)
    app (make-app-stack routes db)
  {:port (Integer. (nth args 0 3000))
   :route routes
   :dev dev
   :db db
   :app app})

(defn start [state]
  (println "Running in " (if (:dev state) "DEVELOPMENT" "PRODUCTION") " mode")
  (model/create-tables (:db state))
  (jetty/run-jetty (:app state) {:port (:port state)}))

(defn -main [& args]
  (start (init-state args false)))

make-routes генерирует маршрутизатор на основе библиотеки compojure, а make-app-stack затем упаковывает этот маршрутизатор в связку промежуточного программного обеспечения, которая вводит глобальное состояние (например, строку БД) для использования обработчиками.

Так как мне добавить wrap-reload к этой настройке? Макрос #'app не будет работать с локальной let «переменной» (или как там она называется). Нужно ли выставлять мое приложение как глобальную переменную? Или я могу заново сгенерировать полное замыкание по каждому запросу.

Мои инстинкты говорят мне, чтобы я не держал код глобальной инициализации в теле модуля и держал весь код в main, но, возможно, я слишком много думаю. Должен ли я просто ввести свой код init-state как глобальные и назвать его днем, или есть лучший способ?

1 Ответ

0 голосов
/ 06 января 2019

Я нашел решение, с которым я могу жить.

(ns webdev.core
  (:require [ring.middleware.reload :as ring-reload]))

; Defeat private defn
(def reloader #'ring-reload/reloader)

; Other stuff...

(defn load-settings [args dev]
  {:port (Integer. (nth args 0 3000))
   :db (nth args 1 "jdbc:postgresql://localhost/webdev")
   :dev dev})

(defn make-reloading-app [settings]
  (let [reload! (reloader ["src"] true)]
    (fn [request]
      (reload!)
      ((make-app settings) request))))

(defn start [settings]
  (let
    [app
      (if (:dev settings)
        (make-reloading-app settings)
        (make-app settings))]
    (println "Running in " (if (:dev settings) "DEVELOPMENT" "PRODUCTION") " mode")
    (model/create-tables (:db settings))
    (jetty/run-jetty app {:port (:port settings)})))

(defn -main [& args]
  (start (load-settings args false)))

Полный код доступен здесь: https://github.com/panta82/clojure-webdev/blob/master/src/webdev/core.clj

Вместо непосредственного использования wrap-reload я использую скрытую reload функцию. Я должен воссоздавать стек маршрутизации в каждом запросе, но в dev он работает нормально. Глобальное состояние не требуется:)

...