spring-boost icon indicating copy to clipboard operation
spring-boost copied to clipboard

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

  • 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<Copy>("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]].