我正在寻找一种惯用的方法来定义Clojure中可以由外部"服务提供商"实现的接口.我的应用程序将在运行时定位并实例化服务提供者模块,并将某些职责委托给它.
例如,假设我正在实现RPC机制,并且我希望允许在配置时注入自定义中间件.该中间件可以预处理消息,丢弃消息,使用日志包装消息处理程序等.
我知道有几种方法可以做到这一点,如果我回到Java反射,但觉得在Clojure中实现它将有助于我的理解.
(注意,我在这里一般意义上使用SPI,而不是特别指它在JAR文件规范中定义的方式)
谢谢
Compojure使用"中间件"来处理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 {: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 #, :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
Clojure是一种非常动态的语言:几乎任何可以在编译时完成的事情都可以在运行时完成.您的"部署配置"可能只是一个在运行时加载到应用程序中的clojure源文件.只需调用(加载"my-config.clj").请注意,如果您真的想要,您甚至可以覆盖特定动态范围中的函数,因此您可以将任何函数(包括核心函数)与另一个函数(包括记录其参数,返回值以及运行时间)进行包装.有关如何执行此操作的示例,请查看clojure.contrib.trace.