在Clojure中定义SPI

在Clojure中定义SPI,clojure,service-provider,Clojure,Service Provider,我正在寻找一种惯用方法来定义Clojure中可以由外部“服务提供者”实现的接口。我的应用程序将在运行时定位并实例化服务提供者模块,并将某些职责委托给它 比方说,我正在实现一个RPC机制,我想允许在配置时注入一个自定义中间件。该中间件可以预处理消息、丢弃消息、将消息处理程序包装为日志等 如果我回到Java反射,我知道有几种方法可以做到这一点,但我觉得在Clojure中实现它将有助于我的理解 (注意,我在这里使用的是一般意义上的SPI,而不是特别指在 感谢用于处理HTTP请求,您可以看看它的实现。C

我正在寻找一种惯用方法来定义Clojure中可以由外部“服务提供者”实现的接口。我的应用程序将在运行时定位并实例化服务提供者模块,并将某些职责委托给它

比方说,我正在实现一个RPC机制,我想允许在配置时注入一个自定义中间件。该中间件可以预处理消息、丢弃消息、将消息处理程序包装为日志等

如果我回到Java反射,我知道有几种方法可以做到这一点,但我觉得在Clojure中实现它将有助于我的理解

(注意,我在这里使用的是一般意义上的SPI,而不是特别指在

感谢用于处理HTTP请求,您可以看看它的实现。Compojure中的“处理程序”是接受请求并返回响应的函数。(请求和响应都是Clojure哈希映射。)“中间件”是一个接受处理程序函数并返回不同处理程序函数的函数。中间件可以改变请求和/或响应;它可以调用它所传递的处理程序(如果愿意,可以重复调用)或短路并忽略处理程序,等等。您可以以任何组合的方式在其他处理程序中包装处理程序

由于函数是一流的对象,因此它非常轻量级,易于实现和使用。然而,它不会像您从Java接口获得的那样在编译时强制执行任何内容;这完全是一个遵循惯例和鸭子打字的问题。最终可能对这项任务有好处,但它们暂时不可用(可能在Clojure 2.0中?)

不确定这是否是您想要的,但这里有一个非常基本的版本:

;; Handler
(defn default [msg]
  {:from "Server"
   :to (:from msg)
   :response "Hi there."})

;; Middleware
(defn logger [handler]
  (fn [msg]
    (println "LOGGING MESSAGE:" (pr-str msg))
    (handler msg)))

(defn datestamper [handler]
  (fn [msg]
    (assoc (handler msg)
      :datestamp (.getTime (java.util.Calendar/getInstance)))))

(defn short-circuit [handler]
  (fn [msg]
    {:from "Ninja"
     :to (:from msg)
     :response "I intercepted your message."}))

;; This would do something with a response (send it to a remote server etc.)
(defn do-something [response]
  (println ">>>> Response:" (pr-str response)))

;; Given a message and maybe a handler, handle the message
(defn process-message
  ([msg] (process-message msg identity))
  ([msg handler]
     (do-something ((-> default handler) msg))))
然后:

user>(def msg{:从“切斯特”:到“服务器”:消息“你好?”)
#'用户/msg
用户>(处理消息消息)
>>>>响应:{:从“服务器”,:到“切斯特”,:响应“你好”
无
用户>(处理消息消息记录器)
日志消息:{:从“切斯特”,:到“服务器”,:消息“你好?”}
>>>>响应:{:从“服务器”,:到“切斯特”,:响应“你好”
无
用户>(处理消息消息消息(comp logger datestamper))
日志消息:{:从“切斯特”,:到“服务器”,:消息“你好?”}
>>>>回复:{:日期戳#,:从“服务器”,到“切斯特”,回复“你好”
无
用户>(处理消息消息消息(comp短路记录器datestamper))
>>>>回复:{:从“忍者”到“切斯特”,回复“我截获了你的消息。”}
无

Clojure是一种非常动态的语言:几乎任何可以在编译时完成的事情都可以在运行时完成。您的“部署配置”可以是一个clojure源文件,在运行时加载到应用程序中。只需调用(加载“my config.clj”)。请注意,如果您真的愿意,您甚至可以重写特定动态范围中的函数,因此您可以用另一个函数包装任何函数(包括核心函数),该函数表示记录它们的参数、返回值以及它们运行的时间。请查看clojure.contrib.trace以获取如何执行此操作的示例。

感谢您提供的详细答案。Compojure中间件绝对接近我所追求的设计模式。似乎唯一缺少的是间接连接中间件并从应用程序外部将其连接到处理程序的能力,例如,在部署时而不是设计时。
user> (def msg {:from "Chester" :to "Server" :message "Hello?"})
#'user/msg
user> (process-message msg)
>>>> Response: {:from "Server", :to "Chester", :response "Hi there."}
nil
user> (process-message msg logger)
LOGGING MESSAGE: {:from "Chester", :to "Server", :message "Hello?"}
>>>> Response: {:from "Server", :to "Chester", :response "Hi there."}
nil
user> (process-message msg (comp logger datestamper))
LOGGING MESSAGE: {:from "Chester", :to "Server", :message "Hello?"}
>>>> Response: {:datestamp #<Date Fri Nov 27 17:50:29 PST 2009>, :from "Server", :to "Chester", :response "Hi there."}
nil
user> (process-message msg (comp short-circuit logger datestamper))
>>>> Response: {:from "Ninja", :to "Chester", :response "I intercepted your message."}
nil