如何最方便地将状态注入环处理程序(不使用全局变量)?
以下是一个例子:
(defroutes main-routes
(GET "/api/fu" [] (rest-of-the-app the-state)))
(def app
(-> (handler/api main-routes)))
我想将the-state
放入main-routes
的compojure处理程序中。状态可能类似于使用以下内容创建的地图:
(defn create-app-state []
{:db (connect-to-db)
:log (create-log)})
在非环形应用程序中,我将在main函数中创建状态,并开始将其作为函数参数注入到应用程序的不同组件中。
在不使用全局var?
的情况下,可以使用ring的:init
函数完成类似的操作
答案 0 :(得分:21)
我已经看到过这种方式。第一种是使用中间件将状态注入请求映射中的新键。例如:
(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))
如果有选择,我可能会采用第二种方法。
答案 1 :(得分:1)
除了Alex描述的内容外,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])
答案 2 :(得分:-1)
执行此操作的“正确”方法是使用动态绑定的var。您可以使用以下内容定义var:
(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))))
您可以在启动服务器的“main”函数中使用它来注入依赖项:
(def app (-> handler
(wrap-some-state-middleware {:db ... :log ...})))