Imagine I have the following setup:
@org.springframework.stereotype.Component
) inside that application that are written in Clojure and are used by the Camunda workflow engine.I heard that allegedly it is possible to modify the code of a Clojure application without restarting it.
So, I want to
The idea is that I prototype the components from the REPL until they work as designed. This means that the Camunda workflow engine will use the components modified by my actions on the REPL.
Then, I download the current version of the components in the application (so that they are not lost when the application is shut down). This code is then cleaned up, refactored, covered by unit tests and put under version control.
Question:
Update 1
Found following projects which prima facie allow you to interact with Java code using a REPL:
However, I don't know whether you can use them to change the code.
This is a very rough and simple example of how to do this. I have dropped Camunda here because I don't think it's actually relevant.
The approach here:
The full project can be found here
Provide a service, that delegates to some Clojure code:
@Service
class ClojureBackedService {
BigDecimal add(BigDecimal a, BigDecimal b) {
Clojure.var('net.ofnir.repl', 'add').invoke(a, b)
}
}
Start a REPL and load some "good initial setup":
@Service
class ClojureRepl {
@PostConstruct
def init() {
def require = Clojure.var('clojure.core', 'require')
require.invoke(Clojure.read('net.ofnir.repl'))
Clojure.var('clojure.core.server', 'start-server').invoke(
Clojure.read("{:port 5555 :name spring-repl :accept clojure.core.server/repl}")
)
}
@PreDestroy
def destroy() {
Clojure.var('clojure.core.server', 'stop-server').invoke(
Clojure.read("{:name spring-repl}")
)
}
}
For showing how this works, provide a web endpoint, that adds two numbers:
@RestController
class MathController {
private final ClojureBackedService backend
MathController(ClojureBackedService backend) {
this.backend = backend
}
@PostMapping
def add(BigDecimal a, BigDecimal b) {
backend.add(a, b)
}
}
Run the application and do a first test:
$ curl -da=5 -db=5 localhost:8080
10
Looks good. Now connect to the REPL and replace the add
function:
$ telnet localhost 5555
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
user=> (ns net.ofnir.repl)
nil
net.ofnir.repl=> (defn add [a b] (* a b))
#'net.ofnir.repl/add
No call the endpoint again:
$ curl -da=5 -db=5 localhost:8080
25
And there you have your spring service changed at runtime.