在 Clojure 中定义 SPI

发布于 2024-08-12 22:25:30 字数 407 浏览 13 评论 0原文

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

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

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

(注意,我在这里一般意义上使用 SPI,而不是具体指 JAR 文件规范

谢谢

I'm looking for an idiomatic way(s) to define an interface in Clojure that can be implemented by an external "service provider". My application would locate and instantiate the service provider module at runtime and delegate certain responsibilities to it.

Let's say, for example, that I'm implementing a RPC mechanism and I want to allow a custom middleware to be injected at configuration time. This middleware could pre-process the message, discard messages, wrap the message handler with logging, etc.

I know several ways to do this if I fall back to Java reflection, but feel that implementing it in Clojure would help my understanding.

(Note, I'm using SPI in a general sense here, not specifically referring to the way it's defined in the JAR file specification)

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

恰似旧人归 2024-08-19 22:25:30

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 #<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 uses "middleware" to handle HTTP requests, you might look at its implementation. A "handler" in Compojure is a function that takes a request and returns a response. (Request and response are both Clojure hash-maps.) "Middleware" is a function that takes a handler function, and returns a different handler function. Middleware can alter the request, the response, or both; it can call the handler it's passed (repeatedly if it wants) or short-circuit and ignore the handler, etc. You can wrap handlers in other handlers this way in any combination.

Thanks to functions being first-class objects, this is very lightweight and easy to implement and use. However it doesn't enforce anything at compile time as you would get from a Java interface; it's all a matter of following conventions and duck-typing. Protocols might be good for this task eventually, but they are not going to be available for a while (probably in Clojure 2.0?)

Not sure if this is what you want, but here is a very rudimentary version:

;; 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))))

Then:

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
一紙繁鸢 2024-08-19 22:25:30

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

Clojure is a very dynamic language: almost anything that can be done at compile time can be done at runtime. Your "deployment configuration" could simply be a clojure source file that gets loaded into the application at runtime. Just call (load "my-config.clj"). Note that you can even override functions in a particular dynamic scope if you really want to, so you can wrap any function (including core functions) with another one that say logs their arguments, return value and how long they took to run. Have a look at clojure.contrib.trace for an example of how to do this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文