Defining a SPI in Clojure

前端 未结 2 1568
轻奢々
轻奢々 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:41

    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.

    0 讨论(0)
  • 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 #<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
    
    0 讨论(0)
提交回复
热议问题