I totally understand that, I felt the same way first coming to Clojure.

For this situation, there are a couple approaches that you could take
depending on how hardcore you want to be about keeping things functional
& immutable.

The most similar thing you could do to your Java code would be keeping
the token in an atom inside of a connection record.

  (defrecord MyAuthHttpClient [token user psw])


  (defn my-auth-http-client [usr psw]
    (let [token (atom nil)]
      (maybe-update-token token usr psw)
      (->MyAuthHttpClient token usr psw)))


  (defn get-response [client url]
    (maybe-update-token (:token client) (:user client) (:psw client))
    (let [token @(:token client)]
      (do-request token url)))


If you want to do the same thing, but are uncomfortable storing username
and password in the record, you can close them into a function:

  (defrecord MyAuthHttpClient [token refresh-token])


  (defn my-auth-http-client [usr psw]
    (let [refresh-token (fn [t] (maybe-update-token t usr psw))
          token (atom nil)]
      (refresh-token token)
      (->MyAuthHttpClient token refresh-token)))


  (defn get-response [client url]
    ((:refresh-token client) (:token client))
    (let [token @(:token client)]
      (do-request token url)))


If you want polymorphism, such that you can have many authentication schemes
for different HttpClients, and insulate your calling code from those 
details,
use a protocol:

  (defprotocol Client
    (get-response [this url] "Perform an HTTP GET against `url`."))


  (defrecord NoAuthClient []
    Client
    (get-response [this url] (slurp url)))


  ;; E.g.
  (get-response (->NoAuthClient) "https://blockchain.info/stats?format=json";
)
  ;=> "{\"timestamp\":1.589365823E12,\"market_price_usd\":8920.2,..."


  (defrecord MyAuthHttpClient [token refresh-token]
    (get-response [this url]
      (refresh-token token)
      (do-request token url)))


So far, this has all been pretty a pretty typical OO style of abstraction &
separation of concerns. For certain problems, this is really effective.

Now let's imagine that your application grows a lot, you have a dozen
different http clients, and they're all hitting endpoints with different 
rate
limits many times per second. You want to add a rate limiting layer, 
simplify
logging of all of the requests / responses, and make the interface
asynchronous for callers so that they're not blocking for every response.

In that case (in my option), the most effective thing is to make your 
client 
more functional — it shouldn't keep state, perform side effects, etc. All 
of 
that would happen at a higher level of your code, and give you the 
flexibility 
to combine different clients with the rate limit layer and the async request
code however you want. 

Instead, you client would return information about the requests that it 
needs 
made. This is a stronger abstraction with stronger separation of concerns —
the request generation & signing code don't care how or when the requests 
are 
executed, the requesting code doesn't care how requests are built or signed,
and the highest-level code only knows that it made a request — but it's 
significantly more work to define something that precisely.
  
E.g. you might have:

  (defprotocol Client
    (request [this request-data]
      "Build a request and return `this`.")


    (response [this response-data]
      "Return a possibly updated `this` for a response to one of the
      client's requests.")


    (consume-requests [this]
      "Consume all queued requests, return [this requests]."))


  ;; The NoAuthClient doesnt do much...
  (defrecord NoAuthClient [queued]
    Client
    (request [this request-data] (assoc this :queued (conj queued request-
data)))
    (response [this _] this)
    (consume-requests [this] [(assoc this :queued []) queued]))


  ;; Your token auth client, on the other hand...
  (defrecord MyAuthHttpClient
    [token refresh-token requests queued-for-after-refresh]


    Client
    (request [this request-data]
      ;; It is not actually making any requests to refresh, just
      ;; figuring out _what_ needs to be done, and letting the calling
      ;; code take care of _how_ to do it (via clj-http?, aleph?, right
      ;; away?, later?, etc)
      (if (token-expired-given-current-time? token (:ts request-data))
        (-> this
            (update :queued-for-after-refresh conj request-data)
            (update :requests conj (refresh-token token)))
        (update this :requests conj request-data)))


    (response [this response-data]
      (if (is-token-refresh-response? response-data)
        (let [new-token (parse-new-token response-data)

              ;; Any requests that were awaiting a new token are
              ;; now ready, but probably need to have the token
              ;; included
              insert (map #(assoc % :token new-token))
              requests (into requests insert queued-for-after-refresh)]
          (assoc this :token new-token
                      :requests requests
                      :queued-for-after-refresh []))
        this))


    (consume-requests [this]
      [(assoc this :requests []) requests]))






On Tuesday, May 12, 2020 at 2:27:01 AM UTC-5, Scaramaccai wrote:
>
> Hi everyone,
>
> I wanted to give a try to Clojure and functional programming in general 
> but I can't really stop thinking "object oriented" or "with state 
> everywhere". After 20+ years with objects + state I guess I'm lost without 
> them :)
>
> The first thing I want to try is to get some data from an API that needs 
> OAuth authentication. I would like to hide the fact that there's an OAuth 
> token to be sent. In pseudo-Java I would do something like:
>
> class MyAuthHttpClient {
>   private token;
>   public MyAuthHttpClient(String usr, String psw) {...}
>
>   public ... getResponse(Url) {
>   // here check if token is available and if it is expiring;
>   // if expiring -> fetch a new token before call the http service
>   // caller doesn't even know there's a token involved in the process
>   }
> }
>
> What's the proper way to do that in Clojure?
> I guess one way would be to have a function that returns a new token given 
> a (possibly old) token and user+psw
>
> (defn gettkn [usr, psw, tkn] (return a new token if tkn is expiring or tkn if 
> not expiring))
>
> (def wrap-gettkn (partial gettkn "myuser" "mypass"))
>
>
> (defn geturl [url, tkn] client/get url {:oauth-token (wrap-gettkn tkn)})
>
>
>
> I can "save" usr and psw, but I always have to "keep" the tkn around at 
> every level;
> while I would like the token to be "hidden" to the "geturl" clients (just 
> like I did in the "private token" in the pseudo-Java).
>
> What's the proper way of doing something like this in Clojure?
>
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/clojure/af5a093b-9f72-4f87-967e-5b8f7df9874f%40googlegroups.com.

Reply via email to