Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between @Controller and RouterFunction in Spring 5 WebFlux

There are two ways to expose HTTP endpoints in spring 5 now.

  1. @Controller or @RestController by making the controller's class, e.g.
@RestController @RequestMapping("persons") public class PersonController {       @Autowired     private PersonRepo repo;      @GetMapping("/{id}")     public Mono<Person> personById(@PathVariable String id){         retrun repo.findById(id);     } } 
  1. Route in @Configuration class by using RouterFunctions:
@Bean public RouterFunction<ServerResponse> personRoute(PersonRepo repo) {     return route(GET("/persons/{id}"), req -> Mono.justOrEmpty(req.pathVariable("id"))                                                                                               .flatMap(repo::getById)                                                  .flatMap(p -> ok().syncBody(p))                                                  .switchIfEmpty(notFound().build())); } 

Is there any performance difference in using anyone approach? Which one should I use when writing my application from scratch.

like image 914
Deepak Kumar Avatar asked Nov 03 '17 09:11

Deepak Kumar


People also ask

What is RouterFunction?

A router function evaluates to a handler function if it matches; otherwise it returns an empty result. The RouterFunction has a similar purpose as a @RequestMapping annotation.

When would you use a functional endpoint?

2. Functional Endpoints are a set of libraries that an application can use to route and handle requests. 3. Functional Endpoints are in charge of request handling from start to finish whereas Annotated Controllers declare intent through annotations and being called back.

What is a spring controller?

Spring Controller annotation is typically used in combination with annotated handler methods based on the @RequestMapping annotation. It can be applied to classes only. It's used to mark a class as a web request handler. It's mostly used with Spring MVC applications.

How do I use WebFlux spring?

Use a simple domain model – Employee with an id and a name field. Build a REST API with a RestController to publish Employee resources as a single resource and as a collection. Build a client with WebClient to retrieve the same resource. Create a secured reactive endpoint using WebFlux and Spring Security.


2 Answers

Programming Paradigm: Imperative vs Functional

In the case with the @Controller or @RestController annotations, we agree with the annotation-based model where we use annotations for mappings (and not only) and as a result side effects (that is not allowed in the functional world) to make our API works. Such side effects could be @Valid annotation that provides inbuilt bean validation for requests' bodies or @RequestMapping with the root path for the whole controller.

On the other hand, with the router functions, we get rid of annotations that consist of any side effects in terms of API implementation and delegate it directly to the functional chain: router -> handler. Those two are perfectly suited for building the basic reactive block: a sequence of events and two protagonists, a publisher and a subscriber to those events.

MVC Legacy: Servlets Stack vs Netty Stack

When we are talking about @Controller I would say that we usually will think in term of synchronous Java world: Servlets, ServletContext, ServletContainerInitializer, DispatcherServlet etc. Even if we will return Mono from a controller to make our application reactive we still will play in terms of Servlet 3.0 specification that supports java.nio.* and running on the same servlets containers such as Jetty or Tomcat. Subsequently, here we will use corresponding design patterns and approaches for building web apps.

RouterFunction on the other hand was inspired by the true reactive approach that originates from the async Java world - Netty and its Channel Model.

Subsequently new set of classes and their APIs for reactive environment emerged: ServerRequest, ServerResponse, WebFilter and others. As for me, they were designed by the Spring team in accordance with the previous years of maintaining the framework and understanding new web systems requirements. The name for those requirements is Reactive Manifesto.

Use Case

Recently my team faced the issue that it is impossible to integrate Swagger with RouterFucntion endpoints. It could upvote for @Controlers, but the Spring team introduced their solution - Spring REST Docs that could be easily connected to reactive WebTestClient. And I use here word 'connected' cause it follows true reactive meaning behind: instead of Swagger with its overloaded configurations and side-effect annotations, you easily could build your API docs in tests without touching your working code at all.

Update 2020: Despite since now Spring Webflux already could be integrated with Swagger subsequently using OpenAPI specification, it still lacks configuration simplicity and transparency that, in my humble opinion, is the consequence of being a part of the archaic MVC approach.

Closure (opinion)

Cause of no performance impact it's likely to hear something similar to 'it is absolutely based on individual preference what to use'. And I agree that it's individual preference indeed among two options: moving forward or moving backwards when you let yourself stay in the same domain for a decade. I think that reactive support for @Controller was done by the Spring team to make it possible for old projects to somehow be in tune with requirements of time and have at least the opportunity for the migration. If you are going to create a web application from scratch then do not hesitate and use the introduced reactive stack.

like image 171
Serhii Povísenko Avatar answered Sep 20 '22 14:09

Serhii Povísenko


Though it's a bit late, but this may be useful for future readers.

By switching to a functional route declaration:

  1. you maintain all routing configuration in one place
  2. you get almost the same flexibility as the usual annotation-based approach in terms of accessing incoming request parameters, path variables, and other important components of the request
  3. you get an ability to avoid the whole Spring Framework infrastructure being run which may decrease the bootstrapping time of the application

Regarding point 3, there are some cases where the whole functionality(IoC, annotation processing, autoconfiguration) of the Spring ecosystem may be redundant, therefore decreasing the overall startup time of the application.

In the era of tiny microservices, Amazon Lambdas, and similar cloud services, it is important to offer functionality that allows developers to create lightweight applications that have almost the same arsenal of framework features. This is why the Spring Framework team decided to incorporate this feature into the WebFlux module.

The new functional web framework allows you to build a web application without starting the whole Spring infrastructure. The main method in that case should be somewhat like the following(note, there is no @SpringBootApplication annotation)

class StandaloneApplication {      public static void main(String[] args) {          HttpHandler httpHandler = RouterFunctions.toHttpHandler(            routes(new BCryptPasswordEncoder(18))         );           ReactorHttpHandlerAdapter reactorHttpHandler = new ReactorHttpHandlerAdapter(httpHandler);           HttpServer.create()              .port(8080)              .handle(reactorHttpHandler)              .bind()              .flatMap(DisposableChannel::onDispose)              .block();      }      static RouterFunction<ServerResponse> routes(PasswordEncoder passwordEncoder ) {          return             route(                 POST("/check"),                  request -> request                            .bodyToMono(PasswordDTO.class)                           .map(p -> passwordEncoder                                .matches(p.getRaw(), p.getSecured()))                            .flatMap(isMatched -> isMatched                                ? ServerResponse                                    .ok()                                    .build()                                : ServerResponse                                    .status(HttpStatus.EXPECTATION_FAILED)                                    .build()                             )                  );      } } 
like image 28
Suren Aznauryan Avatar answered Sep 21 '22 14:09

Suren Aznauryan