3

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 partialed 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: bindings:

(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.

| improve this question | | | | |
  • Wrap it in a protocol if you want to feel more Clojurey. – kdgregory Aug 8 '17 at 0:08
  • @kdgregory according to the linked page, protocols are meant for abstractions. That means that it would be useful if I had more than one type of credentials... but I don't. – Moon Cheesez Aug 11 '17 at 12:35
  • 1
    @MoonCheesez "this looks a lot like what you would do in object-oriented programming" So what? – Stop harming Monica Dec 9 '17 at 17:38
  • 1
    @MoonCheesez Your clojure code is pure, isn't it? – Stop harming Monica Dec 11 '17 at 8:02
  • 1
    Just FTR: (1) Yes, "objects" are families of partially-applied functions, usually namespaced under that object; (2) No, the partially-applied state is not required to be mutable. (3) Objects are not a problem; uncontrolled mutable state and uncontrolled effects are. You can have objects that play nice both with state (e.g. immurtable) and with effects (e.g. in a monadic way). – 9000 Jan 29 at 17:20
2

OO requires objects have mutable attributes

No, OO requires objects have attributes. There is a lot of OO code that uses objects with mutable attributes, and also a lot of OO code that uses objects with only immutable attributes.

Take a look at Java's String class, all the operations give you new Strings, rather than mutating the content of this.

| improve this answer | | | | |
0

Not sure if it is "functional" enough for you but you could replace call-api with call-auth-api and in that have it obtain credentials, possibly from a duration limited store, and supply them as one of the inputs to the actual call-api.

| improve this answer | | | | |
  • I think implementing a duration limited store can increase the complexity greatly as managing the store, especially expiring the store, will require a constant checking. Aside from that, pulling data from an external source in a function is not really functional... – Moon Cheesez Aug 11 '17 at 12:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.