Передача состояния в качестве параметра обработчику звонка?

Как наиболее удобно внедрить состояние в обработчики кольца (без использования глобальных переменных)?

Вот пример:

(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 ...})))
Другие вопросы по тегам