Defining a SPI in Clojure

前端 未结 2 1641
轻奢々
轻奢々 2021-01-14 04:09

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 instanti

2条回答
  •  谎友^
    谎友^ (楼主)
    2021-01-14 04:54

    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 #, :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
    

提交回复
热议问题