Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/jaju/spring-boost

Introduce Clojure and live-coding power to your Spring Boot application!
https://github.com/jaju/spring-boost

clojure nrepl reactive springboot2 webflux

Last synced: 19 days ago
JSON representation

Introduce Clojure and live-coding power to your Spring Boot application!

Lists

README

        

* What is spring-boost?

Ever wished to introduce Clojure into your SpringBoot app (organization)?
And, want to start very small - ~nrepl~ and a ring-like sub-module - and then use REPL-power for live-coding and adding new functionality?

Then, ~spring-boost~ simplifies it for you!

A complete example of how it works can be found at [[https://github.com/jaju/spring-boost-example][jaju/spring-boost-example]].

** Caveat
For the web-endpoints, the library assumes *Spring Webflux*. It will not work with standard, thread-ed spring-boot.
But ~nREPL~ will work just fine irrespective.

* The Pitch: Sample Application Code
Here's how you can write Clojure code, using *Compojure*, with Springboot.
The interface is _ring like_ - Mostly ring, but with a few quirks that should not matter for most common use-cases.

** What will my Clojure look like?
Here is an example code that you can use in your SpringBoot application!
It works along-side your existing Java code, without interfering, and you can access Clojure from your Java.

The following code shows
1. Setting up end-points - routes and all - with Compojure
2. Setting up a websocket handler - builds on top of Springboot's websocket infrastructure.

#+begin_src clojure
(ns org.msync.spring-clj.core
(:require [org.msync.spring-boost :as boost]
[compojure.core :refer :all]
[compojure.route :refer [not-found]]
[clojure.string])
(:import [java.util.logging Logger]
[org.springframework.context ApplicationContext]))

(defonce logger (Logger/getLogger (str *ns*)))

(defroutes app
"Root hello-world GET endpoint, and another echo end-point that handles both GET and POST.
The :body entry in the request-map comes in either as a map for JSON requests, or as a String
for other types."
(GET "/" [:as {query-string :query-string}]
(str "

Hello World.

"
(if-not (clojure.string/blank? query-string) (str "We received a query-string " query-string))))
(GET "/echo/:greeter" [greeter]
{:status 200
:headers {:content-type "application/json"}
:body {:greeting (str "Hello, " greeter)}})
(POST "/echo/:greeter" [greeter :as request]
{:status 200
:headers {:content-type "application/json"}
:body {:greetings (str "Hello, " greeter)
:echo (:body request)}})
(not-found "

Page not found

"))

(defn web-socket-handler [session]
(pr-str session)
;; Use the session as you wish - to create session-specific handlers
(fn [^String message]
(str "Hello, " (.toUpperCase message))))

(defn main
"Set this as your entry-point for the Clojure code in your spring-boot app.
Gets the ApplicationContext object as an argument - which you are free to ignore or use."
[^ApplicationContext application-context]

(.info logger (str "[spring-clj] Initializing clojure app..."))
(boost/set-handler! app)
(boost/set-websocket-handler! web-socket-handler))
#+end_src

Note that the paths are relative to the base path set in ~application.yml~. Hence, ~/echo/:greetings~ will be accessible at ~/clojure/echo/:greetings~.

** Configure Gradle
*** Modify ~build.gradle.kts~ (or, ~build.gradle~)
This is the Gradle-Kotlin version.
#+begin_src kotlin
repositories {
maven {
name = "Clojars"
url = uri("https://clojars.org/repo")
}
}

dependencies {
developmentOnly("org.msync:spring-boost:0.2.0-SNAPSHOT")
developmentOnly("compojure:compojure:1.6.3")
}
#+end_src
*** Ensure your clojure code is copied to the classpath
Assuming you will write your clojure code in ~src/main/clojure~

#+begin_src kotlin
tasks.register("copyClojure") {
into("build/classes/java/main") {
destinationDir = file(".")
from("src/main/clojure")
}
}

tasks.getByName("bootRun").dependsOn("copyClojure")
tasks.getByName("bootJar").dependsOn("copyClojure")
#+end_src

** Hint Springboot using ~@ComponentScan~
#+begin_src java
package com.company.my_application;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("org.msync.spring_boost")
// And other standard stuff
public class YourApplication {
// Your code here
}
#+end_src

** Modify ~application.yml~ (or ~application.properties~)
By default, port 7888 is used. But add ~clojure-component.nrepl-port~ to your ~application.yml~ (or equivalent) file as follows

#+begin_src yaml
# ...
clojure-component:
nrepl-port: 8190
nrepl-start: true
root-path: /clojure
ws-path: /ws
init-symbol: org.msync.spring-clj.core/main
# ...
#+end_src

** Run "bootRun"

And, *run*!

#+begin_src bash
./gradlew bootRun
#+end_src

And you should see something like the following
#+BEGIN_EXAMPLE
...
[2021-09-10 12:08:14,182] INFO [main] org.msync.spring_boost.application_context$_component_init::invokeStatic Initializing the ClojureComponent
[2021-09-10 12:08:14,984] INFO [main] org.msync.spring_boost.Boost::startNrepl nREPL server started on port = 8190
[2021-09-10 12:08:14,986] INFO [main] org.msync.spring_boost.Boost::setupAppInit Initializing clojure code: org.msync.spring-clj.core/main
[2021-09-10 12:08:21,097] INFO [main] jdk.internal.reflect.NativeMethodAccessorImpl::invoke0 [spring-clj] Initializing clojure app...n
...
#+END_EXAMPLE

* Connect to the NREPL

Starting ~nREPL~ by default can be controlled via configuration. But you can easily start/stop ~nREPL~ using two exposed end-points, that take *POST* requests.

For your convenience, there's a namespace you can switch to and get hold of the ~ApplicationContext~ object via the /state/ atom's ~:ctx~ key.

#+begin_src clojure
user> @org.msync.spring-boost.application-context/state
;; =>
{:ctx #object[org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
0x333bd779
"org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext@333bd779, started on Wed Sep 01 21:47:28 IST 2021"]}
#+end_src

** Control the NREPL server
*** Start it
#+begin_src bash
curl -XPOST http://host:port/clojure/nrepl-start
#+end_src

*** Stop it
#+begin_src bash
curl -XPOST http://host:port/clojure/nrepl-stop
#+end_src

* License

Copyright © 2020-22 - Ravindra R. Jaju

This program and the accompanying materials are made available under the
terms of the Eclipse Public License 2.0 which is available at
[[http://www.eclipse.org/legal/epl-2.0][http://www.eclipse.org/legal/epl-2.0]].

This Source Code may also be made available under the following Secondary
Licenses when the conditions for such availability set forth in the Eclipse
Public License, v. 2.0 are satisfied: GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or (at your
option) any later version, with the GNU Classpath Exception which is available
at [[https://www.gnu.org/software/classpath/license.html][https://www.gnu.org/software/classpath/license.html]].