Clojure Web 开发
HTTP 服务
Clojure 中,一般使用库将 HTTP 1请求抽象成一个普通的 map,其中包含 uri, method, headers, body(可以参照这类抽象的一个流行库 ring 对请求的抽象),然后书写针对这个 map 输入的函数用于处理该请求。
(fn [_req] {:status 200 :body "hello world"})
不过,一般我们会引入一个路由管理的库,来将请求 dispatch 到特定的请求处理函数,目前最为流行的路由管理的库应该就是 reitit 了2。
(require '[reitit.core :as r])
(def router (r/router
[["/api/ping" ::ping]
["/api/orders/:id" ::order]]))
(r/match-by-path router "/api/ping")
;; #Match{:template "/api/ping"
;; :data {:name ::ping} ; 很容易对路由给定一个请求的处理函数
;; :result nil
;; :path-params {}
;; :path "/api/ping"}
另外,有时希望执行一些通用的请求处理功能,比如校验请求验证信息(如请求头 Authentication 中的 token 信息),我们可以在自己的业务处理函数中调用相关逻辑,不过一般会通过 middleware3 或者 interceptor4 这种方式复用这部分逻辑。
;; a middleware
(defn wrap-check-auth [h tokens]
(fn [{:keys [request] :as req}]
(let [authed? (some-> request (get-in [:headers "Authorization"]) tokens)]
(h (assoc req :authed? authed?)))))
(defn hello [{:keys [authed?] :as req}]
{:status 200 :body (str "hello " authed?)})
(defn bye [{:keys [authed?] :as req}]
{:status 200 :body (str "bye " authed?)})
(def tokens #{"token-0"})
(def hello' (wrap-check-auth hello tokens))
(def bye' (wrap-check-auth bye tokens))
诸如 cookie 解析,form 数据解析,内容格式协商与转换(content negotiation)这类常见功能都有现成的 middleware/interceptor 的库5。
Clojure 也有一些类似 web 开发框架的库,比如 kit-clj, Luminus,可以使用它们生成项目,很容易从这些生成的项目入手开始定制你自己的项目。
状态管理
一种常见的实践是,我们将一个启动的应用看成是一些相互依赖的组件(其中一些是状态组件)构成的系统。
手工维护这些组件依赖关系与它们初始化是一件无聊的事情,有一些库帮助我们实现这个操作:Integrant, Component, Mount.
特别的,Integrant 更是允许你将应用系统的构建编程数据驱动的。
(require '[integrant.core :as ig])
;; 实现组件 :adapter/jetty & :handler/greet
(defmethod ig/init-key :adapter/jetty [_ {:keys [handler] :as opts}] ...)
(defmethod ig/init-key :handler/greet [_ {:keys [name]}] ...)
;; 系统配置(数据)
(def config {:adapter/jetty {:port 8080, :handler #ig/ref :handler/greet}
:handler/greet {:name "Alice"}})
;; 配置 -> 运行时系统
(def system (ig/init config))
Stuart Sierra 的 reloaded workflow 这篇文章也值得一读。