The challenge
Last weekend, I decided to explore more about how Clojure can interact with the existent Java ecosystem, the challenge was simple:
Create a simple web framework in Clojure using the Quarkus framework as a base.
Premises:
- The Quarkus does not know what will be executed by route, even not how many routes exist.
- I should be able to create routes and handlers using Clojure as simply as possible:
(defn send-hello-world [] "Hello World")
(defn routes [] [{:method "GET"
:path "/hello"
:handler send-hello-world}])
Some definitions:
- Quarkus: Quarkus is a full-stack, Kubernetes-native Java framework made for Java virtual machines (JVMs) and native compilation, optimizing Java specifically for containers.
- Clojure: Is a dynamic, general-purpose programming language
Step 1: Creating a Quarkus app
It is very easy to start an app in Quarkus you can follow this tutorial, as you can see your last command will be quarkus create && cd code-with-quarkus
after that you can open the folder code-with-quarkus with your favorite IDE, the command created the basic structure of a Quarkus app, and you can run with quarkus dev
Step 2: Enable the project to recognize clj files
You need to configure Quarkus to include .clj files in target folder (The folder with your compiled app), and you can do it by adding this configuration in pom.xml
inside <project>
<resources>
<resource>
<directory>/</directory>
<includes>
<include>*.clj</include>
</includes>
</resource>
</resources>
Step 3: Create Clojure file
As I mentioned earlier, I defined one structure to declare my routes, in the same location of the folder main
. Then I created a folder named quarkus_clj
with a file called core
with the code below:
(ns quarkus-clj.core)
(defn send-hello-world [] "Hello World")
(defn routes [] [{:method "GET"
:path "/hello"
:handler send-hello-world}])
Step 4: Enabling Quarkus to handle my Clojure program
Here is where the magic happens 🎩🪄!
First of all, you should install the Clojure inside your Quarkus app; you can do it by adding a dependency in pom.xml
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.11.1</version>
</dependency>
Now, you can delete the file GreetingResource.java
and its tests. In the same place, create a file Starting.java
I write some comments explaining how it works
@ApplicationScoped
public class Starting {
//Setup app routes
public void setupRouter(@Observes Router router) {
// Load Clojure core;
IFn require = Clojure.var("clojure.core", "require");
// Load quarkus-clj.core namespace
require.invoke(Clojure.read("quarkus-clj.core"));
// Load the route list function
IFn routesFn = Clojure.var("quarkus-clj.core", "routes");
// Invoke the function with no parameters
PersistentVector routesVector = (PersistentVector) routesFn.invoke();
//For each route in routes vector
for (Object route : routesVector) {
/**Get the route map, example
{:method "GET"
:path "/hello"
:handler send-hello-world}
*/
PersistentArrayMap routeMap = (PersistentArrayMap) route;
//Get :path value
String path = (String) routeMap.valAt(Clojure.read(":path"));
//Get :handler function
IFn handlerRoute = (IFn) routeMap.valAt(Clojure.read(":handler"));
//Get :method value
String method = (String) routeMap.valAt(Clojure.read(":method"));
//Create a handler to exec handler function
Handler<RoutingContext> handlerFn = (RoutingContext context) -> {
String result = (String) handlerRoute.invoke();
context.response().end(result);
};
//Config the route in quarkus
router.route(HttpMethod.valueOf(method), path).handler(handlerFn);
}
}
}
Now you can just run: quarkus dev
open your declared route and see the result!
Conclusion
This was a quick example of how to use Clojure inside a Quarkus app to create dynamic routes. With just a few steps, we connected the two ecosystems and set up a basic routing system. Feel free to expand on this foundation and explore other possibilities with Clojure and Quarkus!
Top comments (2)
// Load the route list function
IFn routesFn = Clojure.var("quarkus-clj.core", "route");
I guess there is a typo. it has to be "routes". right?
Yees! I'll fix