Введение в функциональную веб-платформу весной 5

Введение в функциональную веб-платформу весной 5

1. Вступление

Фреймворк Spring WebFlux представляет новый функциональный веб-фреймворк, построенный с использованием реактивных принципов.

В этом руководстве мы узнаем, как работать с этим фреймворком на практике.

Мы будем основывать это на существующем руководствеGuide to Spring 5 WebFlux. В этом руководстве мы создали небольшое реактивное REST-приложение с использованием компонентов на основе аннотаций. Здесь мы воспользуемся функциональной структурой.

2. Maven Dependency

Нам понадобится та же зависимостьspring-boot-starter-webflux, как определено в предыдущей статье:


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

3. Функциональная веб-платформа

Функциональная веб-платформа представляет новую модель программирования, в которой мы используем функции для маршрутизации и обработки запросов.

В отличие от модели на основе аннотаций, в которой мы используем сопоставления аннотаций, здесь мы будем использоватьHandlerFunction иRouterFunctions.

Кроме того, функциональная веб-структура построена на том же реактивном стеке, на котором была построена основанная на аннотациях реактивная структура.

3.1. HandlerFunction

HandlerFunction представляет функцию, которая генерирует ответы на запросы, направленные к ним:

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

Этот интерфейс в первую очередь представляет собойFunction<Request, Response<T>>, который ведет себя очень похоже на сервлет.

Хотя по сравнению со стандартным сервлетомServlet.service(ServletRequest req, ServletResponse res),HandlerFunction возвращает ответ вместо того, чтобы принимать его в качестве параметра, что делает его свободным от побочных эффектов и упрощает его тестирование и повторное использование.

3.2. RouterFunctionс

RouterFunction служит альтернативой аннотации@RequestMapping. Он используется для маршрутизации входящих запросов к функциям обработчика:

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

Обычно мы можем импортироватьRouterFunctions.route(), вспомогательную функцию для создания маршрутов вместо того, чтобы писать полную функцию маршрутизатора.

Это позволяет нам маршрутизировать запросы, применяяRequestPredicate.. Когда предикат совпадает, возвращается второй аргумент, функция-обработчик:

public static  RouterFunction route(
  RequestPredicate predicate,
  HandlerFunction handlerFunction)

ВозвращаяRouterFunction,route() можно объединить в цепочку и вложить для построения мощных и сложных схем маршрутизации.

4. Реактивное приложение REST с использованием функциональной сети

В нашемguide to Spring WebFlux tutorial мы создаем небольшое приложениеEmployeeManagement REST, используя аннотированный@RestController andWebClient. 

Теперь давайте создадим то же приложение, используя функцииRouter иHandler.

Для начала,we’ll create routes using RouterFunction to publish and consume our reactive streams of Employee. Routes зарегистрированы как bean-компоненты Spring и могут быть созданы внутри любого класса, который будет использоваться в качестве конфигурации Spring.

4.1. Единый ресурс

Давайте создадим наш первый маршрут, используяRouterFunction t, который публикует единственный ресурсEmployee:

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

Чтобы разбить его, первый аргумент определяет HTTP-запрос GET, который будет вызыватьhandler. I.e. вернуть совпавшийEmployee изemployeeRepository только, если путь равен/employee/{id} и запрос имеет тип GET.

4.2. Коллекционный ресурс

Затем для публикации ресурса коллекции добавим еще один маршрут:

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

4.3. Обновление одного ресурса

Наконец, давайте добавим маршрут для обновленияEmployee resource:

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

5. Составление маршрутов

Мы также можем составлять маршруты вместе в одной функции маршрутизатора.

Давайте объединим наши маршруты, созданные выше:

@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())));
}

Здесь мы использовалиRouterFunction.and() to для объединения наших маршрутов.

Наконец, мы создали все REST API, используяRouter иHandler t, которые нам нужны для нашего приложенияEmployeeManagement . Для запуска нашего приложения мы можем использовать разные маршруты или один составной, который мы создали выше.

6. Тестирование маршрутов

Мы можем использоватьWebTestClient для тестирования наших маршрутов.

Чтобы протестировать наши маршруты с помощьюWebTestClient we, необходимо связать наши маршруты с помощьюbindToRouterFunction and build нашего тестового экземпляра клиента.

Давайте проверим нашgetEmployeeByIdRoute:

@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);
}

и аналогичноgetAllEmployeesRoute:

@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);
}

Мы также можем проверить нашupdateEmployeeRoute, заявив, что обновленный синстансEmployee обновляется черезEmployeeRepository:

@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);
}

Для получения дополнительных сведений о тестировании с помощьюWebTestClient обратитесь к нашему руководству поworking with WebClient and WebTestClient.

7. Резюме

В этом руководстве мы представили новый функциональный веб-фреймворк в Spring 5, мы изучили его две основные функцииRouter andHandler и узнали, как создавать различные маршруты для обработки запроса и отправки ответа.

Кроме того, мы воссоздали наше приложениеEmployeeManagement , представленное вGuide to Spring 5 WebFlux, с нашей новой структурой.

Основываясь наReactor, реактивный фреймворк полностью обеспечит реактивный доступ к хранилищам данных. К сожалению, большинство хранилищ данных пока не предоставляют такой реактивный доступ, за исключением нескольких баз данныхNoSQL, таких какMongoDB.

Как всегда, полный исходный код можно найтиover on Github.