2
votes

I am new to Clojure, so perhaps this is a really obvious question, but I cannot find any useful answer.

I am implementing a REST service with Spring Boot which uses Clojure to calculate the result by means of multiple calls to Compiler.eval(). I have some Clojure stuff that is supposed to be shared among all requests (constants, functions, global variables, etc.), and well as some per-request stuff that should be isolated and unique for each request.

I run this service as executable JAR with clojure.jar in the classpath. However, from JVM's perspective, you can only access Clojure through static methods, which means there is only one "instance of Clojure" per JVM. This means that if you eval (def a 1) during one request, "a" becomes visible to other requests as well.

Question 1: where and how should I define dynamic (request-scoped) variables/symbols in Clojure?

Question 2: Suppose I want a completely "new" Clojure instance for every request. How could I achieve that? So for example, I could have the variable with the same name and in the same namespace, but with different values, in the same JVM? I know I can load a whole new set of Clojure classes by using a fresh classloader, but that seems horribly cumbersome.

2
As for 1) i'd use invoke and pass that per-request-state incfrick

2 Answers

3
votes

You shouldn't use global variables in Clojure, unless it is needed to keep track of some global "state", which has to be shared among all functions. It's a functional language (well, mostly) and the functional way of doing things is passing all required stuff along as arguments. That's how you get referential transparency and how you avoid functions seeing and influcencing each-other's values.

I don't know why you want to use SpringBoot for this, but maybe you could take a look at how Clojure-native web libraries work. They usually pass along some kind of "request context" to the handlers, where all necessary information is stored.

For instance Yada or Ring.

So to answer your (1): pass them in with each request. And (2): you don't need that if you pass in new values per request.

0
votes

Although using globals that are local to one request (pardon the ugly expression) is not a generally recommended way to do web services in Clojure, let's say you have a valid use case for this. One such use case could be building an online REPL like tryclj.com.

A possibility to isolate requests would then be to create a temporary (random) namespace for each request, and wipe the namespace after each request. tryclojure does this for requests using clojail for sandboxing (snippet from src/tryclojure/models/eval.clj):

(defn make-sandbox []
  (sandbox try-clojure-tester
           :timeout 2000
           :init '(do (require '[clojure.repl :refer [doc source]])
                      (future (Thread/sleep 600000)
                              (-> *ns* .getName remove-ns)))))