Spring: your next Java microframework

In this article we will talk about a new concept in the upcoming, 5th Spring Framework release called Functional Web Framework and look how it can help in building lightweight microservcies.

You might have been suprised to see Spring and Microframework in the same sentence. But that is right. Spring 5 might easily become your next Java web microframework. To avoid confusion let’s define what do we mean by micro:

Although Spring Boot makes some of these points valid even now, it introduces quite a lot of magic in addition to Spring MVC itself. Even the @Controller annotation is a bit obscure, not even talking about auto-configuration and component scans. Generally this is a gift for a larger scale applications, when Spring takes care about DI, routing, configuration etc. However in microservices world, where applications are tiny cogs in the machine, Spring Boot power might be overkill.

To address this, Spring team introduced a concept called Functional Web Framework - and this is what are we going to talk about. This is a part of bigger project called Spring WebFlux (former Spring Reactive Web).

First of all, let’s get back to basics and see what a web-application is and what components we expect from it. Apparently there is a fundamental part - web-server itself. Then to avoid manual parsing request information and then dispatching our app methods we need a router. And then we need a handler - a piece of code that takes a request, does the job, and returns a response. That is it, no more no less. And this is exactly what Spring Functional Web does - strips out all the layers of abstraction (beans, contexts) and focus on the bare minimum. Note that doesn’t mean Spring steps away from Spring MVC model, instead Functional Web just offers yet another option for building web-apps with Spring.

Handler

Let’s look at the example. To start with, go to http://start.spring.io and generate a new project, using Spring Boot 2.0 and Reactive Web as the only dependency. Now we can define our first handler - function that takes request and returns a response.

HandlerFunction hello = new HandlerFunction() {
    @Override
    public Mono handle(ServerRequest request) {
        return ServerResponse.ok().body(Mono.just("Hello"), String.class);
    }
};

So our handler this is an implementation of HandlerFunction interface that defines a method to take a request param (of type ServerRequest) and return ServerResponse object with “Hello” string. Spring also provides convinient builder to construct server response. In our case we use ok() which automatically sets return code to HTTP 200. To construct body we use another concept called Mono which means single reactive value, but let’s not focus on this one right now. Just think about Mono.just(...) as a way to return Publisher (which is effectively a promise) to implement non-blocking paradigm. Reactive Web is a native part of Spring 5 and it is implemented via Java 9’s Reactive Streams. If you’d like to know more reactive date types, please refer to the great Dave Syer’s post.

We also can make the code a bit more concise using Jave 8 lambdas and as HandlerFunction is a single abstract method interface (SAM) we can write it as

HandlerFunction hello = request -> ServerResponse.ok().body(Mono.just("Hello"), String.class);

Router

Now when we have a handler, it is time to define a router. Say we want to invoke our handler when “/” url is requested with GET method. To do this we can define RouterFunction which maps a handler function to a route.

RouterFunction router = route(GET("/"), hello);

route and GET are just static methods from RequestPredicates and RouterFunctions helper classes, they help to build what is called a RouterFunction. Router function simply takes a request, check if that is matches with predicates (path, method, content type etc.) and invokes handler function if it does. Our predicate is - http method is GET and path is “/” and hander function is hello which is defined above.

Web server

Now it is time to put it all together and wind up the complete application. We will use lightweight and simple Reactive Netty as a web-server. To integrate our router into the web-server we need to turn it into a web-server level abstraction, generic HttpHandler.

HttpHandler httpHandler = RouterFunctions.toHttpHandler(router);

And start web-server with this handler:

HttpServer
    .create("localhost", 8080)
    .newHandler(new ReactorHttpHandlerAdapter(httpHandler))
    .block();

ReactorHttpHandlerAdapter is just a Netty class to wrap HttpHandler, but the rest of the code is very straightforward. We create a new web-server listening on address localhost and port 8080 and add our http handler which is effectively just an entry point for our router.

And this is it, application is ready! The full code is:

public static void main(String[] args) throws IOException, LifecycleException, InterruptedException {
    HandlerFunction hello = request -> ServerResponse.ok().body(Mono.just("Hello"), String.class);

    RouterFunction router = route(GET("/"), hello);

    HttpHandler httpHandler = RouterFunctions.toHttpHandler(router);

    HttpServer
            .create("localhost", 8080)
            .newHandler(new ReactorHttpHandlerAdapter(httpHandler))
            .block();

    Thread.currentThread().join();
}

The last line is only needed to keep JVM process running as HttpServer process will not block it. You may notice that application start time is instant, as there is no component scan or auto-configuration happening.

We can run this app as a simple Java application - no application containers needed.

To package the application for deployment, we can still utilise the power of Spring Maven Plugin and simply do: ./mvnw package

This command will produce a fat jar with all dependencies included which can be deployed and executed with only JRE installed.

java -jar target/functional-web-0.0.1-SNAPSHOT.jar

Also if we take a look at the memory usage by the application, it is just under 32 Mb - 22 Mb of metaspace (classes) and about 10 Mb of heap. Of course, the app doesn’t do anything, however this is a very good indicator that framework and runtime require minimum resources to function.

JSON support

In our example we return a string, but it is very easy to return JSON objects as well.

Let’s extend our application with new endpoint that will return JSON. Our data class is very simple - just a string field called name. To avoid Java boilerplate for data classes, we use Project Lombok feature - @Data annotation. By having this annotation on a class we get autogenerated getters, setters, equals and hashCode methods so we don’t have to implement it manually.

@Data
class Hello {
    private final String name;
}

Then, we need to extend our router to provide new response on GET request to “/json” path. This can be done with calling andRoute(...) method on the existing route. Also let’s extract routes definition to a function so it is separate from the application code and can be re-used later in tests.

static RouterFunction getRouter() {
    HandlerFunction hello = request -> ok().body(Mono.just("Hello"), String.class);

    return
        route(
            GET("/"), hello)
        .andRoute(
            GET("/json"), req -> 
                ok()
                .contentType(APPLICATION_JSON)
                .body(Mono.just(new Hello("world")), Hello.class));
}

We also optimised code a bit - handler function is now declared in place, and router and ok() is imported as a static method, which makes code succinct.

After restart, out application will return {"name":"world"} by “/json” path with content type header set to application/json.

Application context

You might have noticed that there is no application context defined here. And that is all right - we just don’t need one! Despite RouterFunction is supported within Spring WebFlux applications, application context is not a requirement anymore to run a simple and lightweight JSON service.

Testing

To test reactive web application, Spring offers new client called WebTestClient (similar to MockMvc). It can be created based on existing application context but as we don’t have one we will bind to our router.

public class FunctionalWebApplicationTests {

    private final WebTestClient webTestClient =
            WebTestClient
                .bindToRouterFunction(
                    FunctionalWebApplication.getRouter())
                .build();

    @Test
    public void indexPage_WhenRequested_SaysHello() {
        webTestClient.get().uri("/").exchange()
                .expectStatus().is2xxSuccessful()
                .expectBody(String.class)
                .isEqualTo("Hello");
    }

    @Test
    public void jsonPage_WhenRequested_SaysHello() {
        webTestClient.get().uri("/json").exchange()
                .expectStatus().is2xxSuccessful()
                .expectHeader().contentType(APPLICATION_JSON)
                .expectBody(Hello.class)
                .isEqualTo(new Hello("world"));
    }
}

WebTestClient has a set of asserts on the returned result to validate status code, body, content type etc.

Conclusion

Spring 5 introduces new paradigm to develop small and lightweight microservices-style web-applications. Such application can function without application context and use microframework approach, when routes and handlers defined explicitely.

Code

Code for this article is available on GitHub