1
votes

I am just starting to work with clojurescript on the node-js side. I'm using it for building command line tools that will run on node. Now that I have my proof of concept already setup and doing more or less what I want is time to organize the code a bit better.

On JS when I need something similar to a configured http client I usually export a single function that accepts the basic parameters and returns an object with methods bound to that parameters (normally using the revealing module pattern). Something similar to creating a new instance on OOP. Here is a small example of how I would do this on JS:

const request = require('request')
module.exports = (user, pass, baseUrl) => {
   const client = request.defaults({baseUrl, auth: {user, pass}})

   const getSomething = (name) => client.get('resources/' + name)
   const createSomething = (name, options) => client.post('resources', {...})
   return { getSomething, createSomething }
}

However on clojurescript I can't find a proper way of doing this. All defines are top level declarations computed at compile time, and making an structure such the one above would require to declare all my functions with a client parameter, then partially apply them and them use them on the body of the logic. This can be something like this:

(ns some-client [:require ["request" :as request]])
(defn get-something [client, name] 
    (.get client (str "resources/" name)))
(defn create-something [client, name, options] 
    (.post client (str "resources") {:name name :data: options}))

(defn make-client [usr, pass, baseUrl] 
    (let [client (.defaults request {:auth {:user usr :pass pass} :baseUrl baseUrl})]
        {:get-something (partial get-something client)
         :create-something (partial create-something client)}))

This may not look so bad, but as soon as you need to use it on another place where all the functions would require such client things start to get messy. You will need to accept the client on all the functions and if that other namespace is just a collection of functions that you will need to use on another place, you will be forced to follow the same schema of return a client creator, accept the client you depend on and make sure you pass it to every function that could need it. I can become as horrible as this:

(ns other-helper)

(defn trivial-stuff [client name bla] 
    (let [get-something (client :get-something)]
     (get-something name))) ; make things like filtering and that


(defn more-trivial-stuff [client name bla] 
    (let [get-something (client :get-something)])
    (get-something name)) ; make things like filtering and that


(defn non-trivial-stuff [client name bla]
  (->>
    (trivial-stuff client name bla)
    (more-trivial-stuff client name)))

(defn more-non-trivial-stuff [client name bla]
    (->>
        (trivial-stuff client name bla)
        (more-trivial-stuff client name)))

(defn compile-utils [client]
 {:more-non-trivial (partial more-non-trivial-stuff client)
  :non-trivial (partial non-trivial-stuff client)})

I can't make any def for the clients because I will need the credentials at runtime, so I have to accept all that stuff as parameters and bind the results, To me that looks like a lot of boilerplate and repetitive code that is not maintainable at all.

Does clojurians have a better approach ? Is any style guide on this regard ? This is the second time I approach clojurescript and it looks very appealing at first, but as soon as you start building non trivial stuff it starts to become messy.

NOTE: for the shake of simplicity I didn't managed any js interop or used channels for async handling. I just declared js objects as normal cljs maps and took everything as it were synchronous, but including js interop and all that stuff will make things even worse.

EDIT (for clarification):

My question is not about if this is possible on clojure, I know it is possible since CLJS and JS share the required set of functionalities to make it possible. However, using the same patter on a totally different language not only feels wrong, but it also looks ugly due to the lisp syntax. The other alternatives I can think about also looks ugly, and involves a lot of repetition because it requires to get the client use it and pass it around on every single function, which leads to very repetitive and distracting code.

To make clear how would I use this on js it will be like this

const makeClient = require('./my-http')
const client = makeClient('my-user','my-pass','http://google.es')
client.getSomething('dude')

As you can see, I can create as many clients with different settings as I may need, I can even make some destructuring and pick only the methods I need since they do not depend on their bindings at all.

1

1 Answers

1
votes

Note: I haven’t used Clojure/Script “in anger” yet, so this is a learning experience for me as well :) I haven't validated the code in a REPL unfortunately.

If I understand correctly, the JS module pattern is a function that returns a dictionary of two functions. At some point in your code you “create” this module, perhaps giving it a name, and you pass it around in your code like so:

let client = require("mymodule")("user", "pass", "http://example.com");
client.getSomething("foo")

You could do the same thing with ClojureScript:

 (defn create-client [user pass base]
    (let [get-something (fn [name] ...)
          create-something (fn [name other] ...)]
       {:get get-something :create create-something}))

 (let [client (create-client "user" "pass" "http://example.com")]
    ((client :get) "foo"))

Now arguably this might look a bit more clunky but it is exactly the same code: close over a few variables, stick two functions in a map, get a function out of a map and call it.


The second part of your question looks like it's about global state – you have to carry around the client object everywhere, and it feels clunky. I don't think though that it looks any better in Javascript?

let client = require("mymodule")("user", "pass", "http://example.com");

let trivialStuff = (client, name, blah) => { client.getSomething(name); ... };

let moreTrivialStuff = (client, name, blah) => { client.getSomething(name); ... };        

let nonTrivialStuff = (client, name, blah) => {

   let result = trivialStuff(client, name, blah)
   return moreTrivialStuff(client, name, result)
}

i.e. you are still passing client around. You could make it a module-level variable after initialisation, but then you lose the ability to make two different clients at runtime.

Are you saying that with the revealing module pattern you would also expose nonTrivialStuff, therefore you could do client.nonTrivialStuff()?


How about creating a namespace with all the functions that expect a client (which could be just a plain map containing the JS requests client), and just using them directly?

e.g.

(ns some-client [:require ["request" :as request]])

(defn make-client [usr pass base-url] 
  {:client (.defaults request {:auth {:user usr :pass pass} :baseUrl baseUrl})}) ;; you might want to use #js here, since you usually cannot pass CLJS maps to JS directly

(defn get-something [client name] 
  (.get (client :client) (str "resources/" name)))

(defn create-something [client name options] 
  (.post (client :client) (str "resources") {:name name :data options}))

Then in other namespaces:

(ns other-code [:require [some-client :as client]])

(def c (atom nil)) 

;; get user and pass from runtime, then call
(reset! c (client/make-client user pass base-url))

;; use like so
(client/get-something @c "name")

I opted into putting the JS client object into a CLJS map to allow for flexibility – in the future you may want to add more data into this map. Of course, the client code would not change since it it should treat it as an opaque value.