Spring Web Frameworks Compared
Spring offers several frameworks to implement server side rendered web pages and REST APIS. In this blog I compare three options:
-
traditional, servlet based (spring-web),
-
reactive, Netty based (spring-webflux) and
-
DSL, reactive, Netty based (spring-jafu)
The accompanying project for this blog can be found at GitHub. Ingredients: Java 11, Maven 3.6+.
I hadn’t looked at Spring, Spring Boot or any of its components for some time. Scala and Kotlin with their respective offerings to implement REST APIs were more interesting (and still are!). Since two years, part of my job has been training less senior Java programmers. To allow these youngsters CV-worthy programming, I decided to give Spring another look and was pleasantly suprised! The Spring people are doing some magnificent work on top of the solid base that is Spring Framework: Spring Boot, Spring Webflux, Spring Cloud, etc.
To introduce Spring Webflux and the still experimental Spring Fu to junior Java programmers was a bit of culture shock, so I thought I would write a gentle introduction to get them started, from the stuff they know to more modern approaches of web development with Spring. A bit like a Rosetta stone.
Spring Web
Annotations, Dependency Injection and more magic.
This is the way we used to develop Spring applications: a @Controller calls methods in one or more @Services, which in turn call methods on @Repositories, other @Services or @Components. All injected by the magic of @Autowired (or @Inject if you like 'plain' javax…). All very nice until you want to debug a heavily annotated program or would like to find out why two candidates were found for injection, but you only defined one. In short: great for most stuff, but the annotation magic is that: magic.
Don’t get me wrong: writing plain Servlets takes a lot of time and gets you deep into boilerplate on different levels of abstraction. And don’t get me started on the original XML-based DI and configuration of Spring!!
An example of how to create some REST endpoints with annotations:
private final ToDoService service;
public ToDoController(final ToDoService service) {
this.service = service;
}
@PostMapping(value = "/todos", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.CREATED)
public ToDoItem add(@RequestBody final ToDoItem todo) {
logger.info("add: {}", todo);
return service.add(todo);
}
@GetMapping(value = "/todos", produces = MediaType.APPLICATION_JSON_VALUE)
public List<ToDoItem> all() {
logger.info("all");
return service.all().toJavaList();
}
@GetMapping(value = "/todos/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ToDoItem> find(@PathVariable("id") final Long id) {
logger.info("find: {}", id);
return service.find(id)
.map(ResponseEntity::ok)
.getOrElse(ResponseEntity.notFound().build());
}
@PutMapping(value = "/todos", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ToDoItem> update(@RequestBody @Valid final ToDoItem item) {
logger.info("update: {}", item);
return Option.of(item)
.filter(todo -> todo.getStatus() != null)
.map(service::update)
.map(ResponseEntity::ok)
.getOrElse(ResponseEntity.notFound().build());
}
@DeleteMapping(value = "/todos")
public ResponseEntity<Void> wipe() {
logger.info("wipe");
service.wipe();
return ResponseEntity.ok().build();
}
No suprises here. Clear, unless you think the meta-programming annotations are in the way of reading the program, which is not a problem in such a small example.
Notice, by the way, that the controller calls the service, which calls the repository where the method-chain is actually just passing the same information from and to the other layers… The next solution will reduce this layering a bit in order to save code.
And I need quite a bit of Vavr to reduce Java streams boilerplate.
Spring Webflux
Years ago I gave a workshop on Reactive Extensions for Java with my colleague Riccardo Lippolis. Since then the standard Scala library or Java with Vavr or Kotlin with Arrow have been my go-to solutions for functional programming tooling. Reactive always needs a bit of getting used to, but when the quarter hits that special spot? Bingo!
Let’s have a look at the same endpoints, but in functional reactive style with Spring Webflux:
@Bean
public RouterFunction<ServerResponse> routes(final ToDoService service) {
var rp = accept(MediaType.APPLICATION_JSON).and(contentType(MediaType.APPLICATION_JSON));
return route()
.POST("/todos", rp, sr -> add(sr.bodyToMono(ToDoItem.class), service))
.GET("/todos", rp, sr -> all(service))
.GET("/todo/{id}", rp, sr -> find(idFromReq(sr), service))
.PUT("/todo", rp, sr -> update(sr.bodyToMono(ToDoItem.class), service))
.DELETE("/todos", sr -> wipe(service))
.build();
}
That’s a clear and concise piece of code. I think it’s much more readable than the annotated version. I’m quite happy with this!
Spring Fu, Spring JaFu
And then there is this experimental project. Spring Fu. It’s like the Webflux version, but even more concise and even more readable. At least, that is what I find.
public static final Consumer<ConfigurationDsl> webConfig = conf ->
conf.beans(beans ->
beans.bean(ToDoHandler.class, () -> new ToDoHandler(beans.ref(ToDoRepository.class)))
).enable(webFlux(server -> {
server.port(8080)
.router(r ->
r.GET("/todos", conf.ref(ToDoHandler.class)::all)
.POST("/todos", conf.ref(ToDoHandler.class)::add)
.GET("/todo/{id}", conf.ref(ToDoHandler.class)::add)
.DELETE("/todos", conf.ref(ToDoHandler.class)::wipe))
.codecs(codecs -> codecs.string().jackson());
}));
Well, that reduces the magic another bit: we see that the Server is configured to listen on port 8080 and that the router is configured with the well known endpoints from the previous two implementations. The "service" bean is created in the beans part, and used via the ref() method of the passed configuration.
No more annotations. No more magic, not even the black kind. Readable, after getting used to the new notations, but that should be fairly quick. Also note that the webConfig is a static and immutable variable, not a method. Another small step towards more immutable code, which will give less headaches in the future.
This is not all, but enough for this blog post. Please have a look at the Github project mentioned earlier. If you see room for improvement, please PR!
Next steps: Kotlin? Spring KoFu looks even better that JaFu! And Kotlin coroutines could be nicer than Reactor…
Also: performance might be interesting: which of the implementations is faster? Which one is more resource-friendly? Most of the time performance is not of special interest as the number of users is low, one or more backend systems are slow or a million other reasons. But it’s still nice to check that we don’t introduce a memory or CPU hog just because we like readability a lot. And a good reason to practice my Gatling skills again.