Einführung in das funktionale Web Framework in Spring 5

Einführung in das Functional Web Framework im Frühjahr 5

1. Einführung

Das Spring WebFlux-Framework führt ein neues funktionales Web-Framework ein, das nach reaktiven Prinzipien erstellt wurde.

In diesem Tutorial erfahren Sie, wie Sie in der Praxis mit diesem Framework arbeiten.

Wir werden dies auf der Grundlage unseres vorhandenen TutorialsGuide to Spring 5 WebFlux erstellen. In diesem Handbuch haben wir eine kleine reaktive REST-Anwendung mit Annotation-basierten Komponenten erstellt. Hier verwenden wir stattdessen den Funktionsrahmen.

2. Maven-Abhängigkeit

Wir benötigen die gleiche Abhängigkeitspring-boot-starter-webflux wie im vorherigen Artikel definiert:


    org.springframework.boot
    spring-boot-starter-webflux
    2.0.3.RELEASE

3. Funktionales Web Framework

Functional Web Framework führt ein neues Programmiermodell ein, in dem wir Funktionen zum Weiterleiten und Behandeln von Anforderungen verwenden.

Im Gegensatz zum annotationsbasierten Modell, bei dem wir Annotationszuordnungen verwenden, werden hierHandlerFunction undRouterFunctions verwendet.

Das funktionale Webframework basiert auf demselben reaktiven Stack, auf dem das annotationsbasierte reaktive Framework basiert.

3.1. HandlerFunction

HandlerFunction stellt eine Funktion dar, die Antworten für an sie weitergeleitete Anforderungen generiert:

@FunctionalInterface
public interface HandlerFunction {
    Mono handle(ServerRequest request);
}

Diese Schnittstelle ist in erster Linie einFunction<Request, Response<T>>, das sich sehr ähnlich wie ein Servlet verhält.

Im Vergleich zu einem Standard-ServletServlet.service(ServletRequest req, ServletResponse res) gibtHandlerFunction die Antwort zurück, anstatt sie als Parameter zu verwenden, wodurch sie nebenwirkungsfrei und einfacher zu testen und wiederzuverwenden ist.

3.2. RouterFunction

RouterFunction dient als Alternative zur Annotation@RequestMapping. Es wird zum Weiterleiten eingehender Anforderungen an Handlerfunktionen verwendet:

@FunctionalInterface
public interface RouterFunction {
    Mono> route(ServerRequest request);
    // ...
}

Normalerweise können wirRouterFunctions.route(), als Hilfsfunktion importieren, um Routen zu erstellen, anstatt eine vollständige Routerfunktion zu schreiben.

Es ermöglicht uns, Anforderungen durch Anwenden vonRequestPredicate. weiterzuleiten. Wenn das Prädikat übereinstimmt, wird das zweite Argument, die Handlerfunktion, zurückgegeben:

public static  RouterFunction route(
  RequestPredicate predicate,
  HandlerFunction handlerFunction)

Durch die Rückgabe vonRouterFunction könnenroute() verkettet und verschachtelt werden, um leistungsstarke und komplexe Routing-Schemata zu erstellen.

4. Reaktive REST-Anwendung mit Functional Web

In unserenguide to Spring WebFlux tutorial erstellen wir eine kleineEmployeeManagement REST-Anwendung mit kommentierten@RestController andWebClient. 

Jetzt erstellen wir dieselbe Anwendung mit den FunktionenRouter undHandler.

Zunächst werdenwe’ll create routes using RouterFunction to publish and consume our reactive streams of Employee. Routes als Spring Beans registriert und können in jeder Klasse erstellt werden, die als Spring-Konfiguration verwendet wird.

4.1. Einzelne Ressource

Erstellen wir unsere erste Route mitRouterFunction , die eine einzelneEmployee-Ressource veröffentlicht:

@Bean
RouterFunction getEmployeeByIdRoute() {
  return route(GET("/employees/{id}"),
    req -> ok().body(
      employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class));
}

Um es aufzuschlüsseln, definiert das erste Argument eine HTTP-GET-Anforderung, die diehandler. aufruft. Geben Sie übereinstimmendeEmployee vonemployeeRepository nur zurück, wenn der Pfad/employee/{id} ist und die Anforderung vom Typ GET ist.

4.2. Sammlungsressource

Als Nächstes fügen wir zum Veröffentlichen der Sammlungsressource eine weitere Route hinzu:

@Bean
RouterFunction getAllEmployeesRoute() {
  return route(GET("/employees"),
    req -> ok().body(
      employeeRepository().findAllEmployees(), Employee.class));
}

4.3. Single Resource Update

Zuletzt fügen wir eine Route zum Aktualisieren vonEmployee resource hinzu:

@Bean
RouterFunction updateEmployeeRoute() {
  return route(POST("/employees/update"),
    req -> req.body(toMono(Employee.class))
              .doOnNext(employeeRepository()::updateEmployee)
              .then(ok().build()));
}

5. Routen erstellen

Wir können die Routen auch zusammen in einer einzigen Routerfunktion zusammenstellen.

Kombinieren wir unsere oben erstellten Routen:

@Bean
RouterFunction composedRoutes() {
  return
    route(GET("/employees"),
      req -> ok().body(
        employeeRepository().findAllEmployees(), Employee.class))

    .and(route(GET("/employees/{id}"),
      req -> ok().body(
        employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)))

    .and(route(POST("/employees/update"),
      req -> req.body(toMono(Employee.class))
        .doOnNext(employeeRepository()::updateEmployee)
        .then(ok().build())));
}

Hier haben wirRouterFunction.and() verwendet, um unsere Routen zu kombinieren.

Schließlich haben wir alle REST-APIs mitRouter undHandler erstellt, die wir für unsereEmployeeManagement -Applikation benötigen. Um unsere Anwendung auszuführen, können wir entweder verschiedene Routen oder eine von uns erstellte Route verwenden.

6. Testrouten

Wir könnenWebTestClient verwenden, um unsere Routen zu testen.

Um unsere Routen mitWebTestClient we zu testen, müssen Sie unsere Routen mitbindToRouterFunction and binden und unsere Testclient-Instanz erstellen.

Testen wir unseregetEmployeeByIdRoute:

@Test
public void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() {
    WebTestClient client = WebTestClient
        .bindToRouterFunction(config.getEmployeeByIdRoute())
        .build();

    Employee expected = new Employee("1", "Employee 1");
    given(employeeRepository.findEmployeeById("1")).willReturn(Mono.just(employee));
    client.get()
        .uri("/employees/1")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(Employee.class)
        .isEqualTo(expected);
}

und ähnlichgetAllEmployeesRoute:

@Test
public void whenGetAllEmployees_thenCorrectEmployees() {
    WebTestClient client = WebTestClient
        .bindToRouterFunction(config.getAllEmployeesRoute())
        .build();

    List employeeList = new ArrayList<>();

    Employee employee1 = new Employee("1", "Employee 1");
    Employee employee2 = new Employee("2", "Employee 2");

    employeeList.add(employee1);
    employeeList.add(employee2);

    Flux employeeFlux = Flux.fromIterable(employeeList);
    given(employeeRepository.findAllEmployees()).willReturn(employeeFlux);

    client.get()
        .uri("/employees")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBodyList(Employee.class)
        .isEqualTo(employeeList);
}

Wir können auch unsereupdateEmployeeRoute testen, indem wir behaupten, dass die aktualisierteEmployee -Sinstanz überEmployeeRepository aktualisiert wird:

@Test
public void whenUpdateEmployee_thenEmployeeUpdated() {
    WebTestClient client = WebTestClient
        .bindToRouterFunction(config.updateEmployeeRoute())
        .build();

    Employee employee = new Employee("1", "Employee 1 Updated");

    client.post()
        .uri("/employees/update")
        .body(Mono.just(employee), Employee.class)
        .exchange()
        .expectStatus()
        .isOk();

    verify(employeeRepository).updateEmployee(employee);
}

Weitere Informationen zum Testen mitWebTestClient finden Sie in unserem Tutorial zuworking with WebClient and WebTestClient.

7. Zusammenfassung

In diesem Tutorial haben wir das neue funktionale Webframework in Frühjahr 5 vorgestellt. Wir haben uns mit den beiden KernfunktionenRouter andHandler and befasst und gelernt, wie verschiedene Routen erstellt werden, um die Anforderung zu verarbeiten und die Antwort zu senden.

Außerdem haben wir unsere inGuide to Spring 5 WebFlux eingeführteEmployeeManagement -Applikation mit unserem neuen Framework neu erstellt.

Wenn das reaktive Framework aufReactor basiert, würde es mit dem reaktiven Zugriff auf Datenspeicher voll glänzen. Leider bieten die meisten Datenspeicher noch keinen solchen reaktiven Zugriff, mit Ausnahme einigerNoSQL-Datenbanken wieMongoDB.

Wie immer kann der vollständige Quellcodeover on Github gefunden werden.