【问题标题】:Defining a SPI in Clojure在 Clojure 中定义 SPI
【发布时间】:2010-12-21 01:56:26
【问题描述】:

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

例如,假设我正在实现 RPC 机制,并且我希望允许在配置时注入自定义中间件。该中间件可以预处理消息、丢弃消息、使用日志记录包装消息处理程序等。

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

(注意,这里我使用的是一般意义上的 SPI,而不是专门指它在 JAR file specification 中定义的方式)

谢谢

【问题讨论】:

    标签: clojure service-provider


    【解决方案1】:

    Clojure 是一种非常动态的语言:几乎可以在编译时完成的任何事情都可以在运行时完成。您的“部署配置”可能只是一个在运行时加载到应用程序中的 clojure 源文件。只需调用(加载“my-config.clj”)。请注意,如果您真的愿意,您甚至可以覆盖特定动态范围内的函数,因此您可以将 any 函数(包括核心函数)与另一个记录其参数、返回值和持续时间的函数包装起来他们跑了。查看 clojure.contrib.trace 以了解如何执行此操作的示例。

    【讨论】:

      【解决方案2】:

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

      由于函数是一流的对象,因此它非常轻量级且易于实现和使用。但是,它不会像从 Java 接口获得的那样在编译时强制执行任何操作;这完全是遵循约定和鸭式打字的问题。 Protocols 最终可能对这项任务有好处,但它们暂时不会可用(可能在 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 #<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
      

      【讨论】:

      • 感谢您的详细解答。 Compojure 中间件绝对接近我所追求的设计模式。似乎唯一缺少的是间接连接中间件并将其从应用程序外部附加到处理程序的能力,例如,在部署时而不是设计时。
      猜你喜欢
      • 1970-01-01
      • 2021-04-16
      • 2020-01-31
      • 2020-09-15
      • 2011-03-14
      • 2020-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多