Передача состояния в качестве параметра обработчику звонка?
Как наиболее удобно внедрить состояние в обработчики кольца (без использования глобальных переменных)?
Вот пример:
(defroutes main-routes
(GET "/api/fu" [] (rest-of-the-app the-state)))
(def app
(-> (handler/api main-routes)))
Я хотел бы получить the-state
в обработчик compojure для main-routes
, Состояние может быть чем-то вроде карты, созданной с помощью:
(defn create-app-state []
{:db (connect-to-db)
:log (create-log)})
В не-кольцевом приложении я бы создал состояние в главной функции и начал бы вводить его или его части в качестве параметров функции в разные компоненты приложения.
Может ли нечто подобное быть сделано с кольцами :init
функция без использования глобальной переменной?
2 ответа
Я видел, как это было сделано несколькими способами. Первый - использование промежуточного программного обеспечения, которое внедряет состояние в качестве нового ключа в карту запроса. Например:
(defroutes main-routes
(GET "/api/fu" [:as request]
(rest-of-the-app (:app-state request))))
(defn app-middleware [f state]
(fn [request]
(f (assoc request :app-state state))))
(def app
(-> main-routes
(app-middleware (create-app-state))
handler/api))
Другой подход заключается в замене вызова defroutes
который за кулисами создаст обработчик и назначит его переменной var с функцией, которая примет некоторое состояние, а затем создаст маршруты, введя состояние в качестве параметров для вызовов функций в определениях маршрутов:
(defn app-routes [the-state]
(compojure.core/routes
(GET "/api/fu" [] (rest-of-the-app the-state))))
(def app
(-> (create-app-state)
app-routes
api/handler))
Если бы у меня был выбор, я бы выбрал второй подход.
В дополнение к тому, что Алекс описал, некоторые фреймворки маршрутизации для ring
есть место для дополнительных аргументов, к которым могут получить доступ все обработчики. Вreitit
это сработает, поместив пользовательские объекты в :data
:
(reiti.ring/ring-handler
(reiti.ring/router
[ ["/api"
["/math" {:get {:parameters {:query {:x int?, :y int?}}
:responses {200 {:body {:total pos-int?}}}
:handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200
:body {:total (+ x y)}})}}]] ]
{:syntax :bracket
:exception pretty/exception
:data {:your-custom-data your-custom-data
:coercion reitit.coercion.spec/coercion
:muuntaja m/instance
:middleware []}}))
В вашем обработчике вы должны работать только с :parameters
, но вы сможете получить доступ к своим персональным данным, выбрав :reitit.core/match
а также :data
. Аргумент, который получает обработчик, полностью основан на следующем:
(defrecord Match [template data result path-params path])
(defrecord PartialMatch [template data result path-params required])
"Правильный" способ сделать это - использовать динамически связанную переменную. Вы определяете переменную с помощью:
(def ^:dynamic some-state nil)
И затем вы создаете некоторое промежуточное программное обеспечение для кольца, которое связывает var для каждого вызова обработчика:
(defn wrap-some-state-middleware [handler some-state-value]
(fn [request]
(bind [some-state some-state-value]
(handler request))))
Вы могли бы использовать это для внедрения зависимостей, используя это в вашей "основной" функции, где вы запускаете сервер:
(def app (-> handler
(wrap-some-state-middleware {:db ... :log ...})))