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-classic simplifies it for you!
Note: This is a version for Spring MVC. For something that goes with Spring WebFlux, look at spring-boost.
For the web-endpoints, the library assumes Spring WebMVC. It will not work with reactive spring-boot. But nREPL will work just fine irrespective.
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.
Very specifically, it makes some limiting choices for the Clojure web-app - the `body` in the `request` map is not an InputStream but one of
- Map (for media-type APPLICATION_JSON)
- org.springframework.util.MultiValueMap (for media-type APPLICATION_FORM_URLENCODED)
- String (for all other cases)
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
- Setting up end-points - routes and all - with Compojure
- Some examples of querying the ApplicationContext and via it, beans.
(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 "<h1>Hello World.</h1>"
(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 "<h1>Page not found</h1>"))
(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))Note that the paths are relative to the base path set in application.yml. Hence, /echo/:greetings will be accessible at /clojure/echo/:greetings.
This is the Gradle-Kotlin version.
repositories {
maven {
name = "Clojars"
url = uri("https://clojars.org/repo")
}
}
dependencies {
developmentOnly("org.msync:spring-boost-classic:0.3.0-alpha3")
developmentOnly("compojure:compojure:1.7.1")
}Assuming you will write your clojure code in src/main/clojure
sourceSets {
main {
resources {
srcDir("src/main/clojure")
}
}
}By default, port 7888 is used. But add clojure-component.nrepl-port to your application.yml (or equivalent) file as follows
# ...
clojure-component:
nrepl-port: 8190
nrepl-start: true
root-path: /clojure
init-symbol: org.msync.spring-clj.core/main
# ...And, run!
./gradlew bootRunAnd you should see something like the following
... 2024-10-02T14:00:27.125+05:30 INFO 76260 --- [ main] org.msync.spring_boost.Boost : nREPL server started on port = 8190 2024-10-02T14:00:27.126+05:30 INFO 76260 --- [ main] o.m.spring-boost.application-context : Initializing the ClojureComponent 2024-10-02T14:00:27.126+05:30 INFO 76260 --- [ main] org.msync.spring_boost.Boost : Initializing clojure code: org.msync.spring-clj.core/main 2024-10-02T14:00:27.639+05:30 INFO 76260 --- [ main] org.msync.spring-clj.core : [spring-clj] Initializing configured clojure web-app... ...
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.
user> @org.msync.spring-boost.application-context/state
;; =>
{:ctx #object[org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
0x6986bbaf
"org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6986bbaf, started on Wed Oct 02 14:00:26 IST 2024"]}flowchart TB
nrepl-client-->nrepl-server
subgraph server [server]
nrepl-server
end
curl -XPOST http://host:port/clojure/nrepl-startcurl -XPOST http://host:port/clojure/nrepl-stopCopyright © 2022-2024 - 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.
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.