I am designing a library to wrap an API with Clojure. The API requires user credentials to authenticate user related calls.
My first approach was to have functions that do each task the API can do:
(defn send-email
[credentials email]
(call-api credentials :email email))
(defn send-message
[credentials message]
(call-api credentials :say message))
However, I noticed that most of the functions have the same argument, credentials
. Hence, I thought of another way to approach this. How about returning functions partial
ed with the credentials after you login:
(defn authenticate
[username password]
(let [credentials (api-auth username password)]
{:credentials credentials
:send-email (partial send-email credentials)
:send-message (partial send-message credentials)})
This makes it much more convenient, the user of this library does not need to add the credentials on every call.
But wait, this looks a lot like what you would do in object-oriented programming. You pass something into a function (the initializer) and you get functions (from an object) that are tailored to what you passed in.
The above could have been written as this in Java:
public class Api {
private String credentials;
Api(String username, String password) {
this.credentials = Utils.api_auth(username, password);
}
void send_email(String email) {
Utils.call_api(this.credentials, Utils.EMAIL, email);
}
void send_message(String message) {
Utils.call_api(this.credentials, Utils.SAY, message);
}
}
As Clojure is a functional programming language, I want to avoid object-oriented programming but it feels as though the whole task was meant for object-oriented programming languages.
How should I design for such an API? Which approach should I choose, or is there something I am not thinking of in my object-oriented mind?
UPDATE
I thought of a third way: binding
s:
(def ^:dynamic *credentials*)
(defn send-email
[email]
(call-api *credentials* :email email))
(defn send-message
[message]
(call-api *credentials* :say message))
; Usage
(binding [*credentials* (api-auth username password)]
(send-email "hello")
(send-message "hello"))
However, the problem with this is that it is very easy to create erroneous codeājust don't bind anything at the start. This is different from implementations of dynamic vars I have seen (pprint
), where there is a default binding.