Spring in Action Covers Spring 5-1--11 — страница 57 из 63

Следовательно, блокирование веб-фреймворков не будет эффективно масштабироваться при большом объеме запросов. Задержка в медленных рабочих потоках усугубляет ситуацию, поскольку для возврата рабочего потока в пул, готового обработать другой запрос, потребуется больше времени. В некоторых случаях такое расположение вполне приемлемо. Фактически, это во многом такой подход, как большинство веб-приложений разрабатывалось более десяти лет. Но времена меняются.

Клиенты этих веб-приложений изменились и не смену людям, иногда просматривающих веб-сайты, пришли люди, часто потребляющих контент и использующих приложения, которые координируются с HTTP API. И в наши дни так называемый Интернет вещей (где люди даже не участвуют) предоставляет автомобили, реактивные двигатели и другие нетрадиционные клиенты, постоянно обменивающиеся данными с веб-API. С увеличением числа клиентов, использующие веб-приложения, масштабируемость становится более важной, чем когда-либо.

Асинхронные веб-инфраструктуры, напротив, обеспечивают более высокую масштабируемость при меньшем количестве потоков - обычно по одному на ядро ЦП. Применяя метод, известный как цикл обработки событий (как показано на рисунке 11.1), эти платформы способны обрабатывать много запросов на поток, что делает затраты на соединение более экономичными.

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

В цикле событий все обрабатывается как событие, включая запросы и обратные вызовы от интенсивных операций, таких как операции с базой данных и сетью. Когда требуется дорогостоящая операция, цикл событий регистрирует обратный вызов для выполнения этой операции параллельно, а затем переходит к обработке других событий.

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

Spring 5 представил неблокирующую асинхронный веб-фреймворк, основанный в основном на своем Project Reactor, для удовлетворения потребностей в большей масштабируемости в веб-приложениях и API-интерфейсах. Давайте взглянем на Spring WebFlux - реактивный веб-фреймворк для Spring.

11.1.1 Описание Spring WebFlux

Когда команда Spring обдумывала, как добавить модель реактивного программирования в веб-слой, быстро стало очевидно, что это будет трудно сделать без большой работы в Spring MVC. Это будет включать в себя код ветвления, чтобы решить, реагировать ли на запросы реактивно или нет. По сути, результатом будут два веб-фреймворка, упакованные как один, с инструкциями if для разделения реактивных и нереактивных действий.

Вместо того чтобы пытаться встроить реактивную модель программирования в Spring MVC, было решено создать отдельный реактивный веб-фреймворк, заимствуя как можно больше от Spring MVC. Итогом стал Spring WebFlux. Рисунок 11.2 иллюстрирует полный стек веб-разработки, определенный Spring 5.

Рис. 11.2 Spring 5 поддерживает реактивные веб-приложения с новым веб-фрэймворком Web Flux, который является родственным Spring MVC и разделяет многие из ее основных компонентов.

В левой стороне рисунка 11.2 вы видите стек Spring MVC, который был представлен в версии 2.5 Spring Framework. Spring MVC (описанный в главах 2 и 6) расположен поверх API сервлетов Java, для которого требуется контейнер сервлета (например, Tomcat).

Spring WebFlux (с правой стороны) не имеет связей с API сервлета, поэтому он строится поверх реактивного HTTP API, который является реактивным приближением той же функциональности, предоставляемой API сервлета. И поскольку Spring WebFlux не связан с API сервлета, для его запуска не требуется контейнер сервлета. Вместо этого, он может работать на любом неблокирующем веб-контейнере, включая Netty, Undertow, Tomcat, Jetty, или любой контейнер Servlet 3.1 или выше.

На рис. 11.2 наиболее примечательным является верхний левый прямоугольник, представляющий компоненты, общие для Spring MVC и Spring WebFlux, в первую очередь аннотации, используемые для определения контроллеров. Поскольку Spring MVC и Spring WebFlux используют одни и те же аннотации, Spring WebFlux во многом неотличим от Spring MVC.

Прямоугольник в верхнем правом углу представляет альтернативную модель программирования, которая определяет контроллеры с функциональной парадигмой программирования вместо использования аннотаций. Более подробно о функциональной модели веб-программирования Spring мы расскажем в разделе 11.2.

Наиболее существенное различие между Spring MVC и Spring WebFlux сводится к тому, какую зависимость вы добавляете в свою сборку. При работе с Spring WebFlux вам необходимо добавить Spring Boot WebFlux стартер вместо стандартного веб стартера (например, spring-boot-starter-web). В файле проекта pom.xml это выглядит так:

org.springframework.boot

spring-boot-starter-webflux

ПРИМЕЧАНИЕ. Как и с большинством Spring Boot стартеров, этот стартер также можно добавить в проект, установив флажок Reactive Web в Initializr.

Интересным побочным эффектом использования WebFlux вместо Spring MVC является то, что встроенным сервером по умолчанию для WebFlux является Netty вместо Tomcat. Netty - один из нескольких асинхронных серверов, управляемых событиями, и он естественным образом подходит для реактивной веб-инфраструктуры, такой как Spring WebFlux.

Помимо использования другой стартер зависимости, методы контроллера Spring WebFlux обычно принимают и возвращают реактивные типы, такие как Mono и Flux, вместо типов доменов и коллекций. Контроллеры Spring WebFlux также могут работать с типами RxJava, такими как Observable, Single и Completable.

РЕАКТИВНЫЙ SPRING MVC?

Хотя Spring WebFlux контроллеры обычно возвращают Mono и Flux, это не означает, что Spring MVC не может работать с реактивными типами. Методы контроллера Spring MVC также могут возвращать Mono или Flux, если хотите.

Разница в том, как эти типы используются. В то время как Spring WebFlux представляет собой действительно реактивный веб-фрэйворк, позволяющую обрабатывать запросы в цикле обработки событий, Spring MVC основан на сервлетах, полагаясь на многопоточность для обработки нескольких запросов.

Давайте включим Spring WebFlux в работу, переписав некоторые из контроллеров API Taco Cloud, чтобы использовать преимущества Spring WebFlux.

11.1.2 Написание реактивных контроллеров

Возможно, вы помните, что в главе 6 вы создали несколько контроллеров для REST API Taco Cloud. Эти контроллеры имели методы обработки запросов, которые имели дело с вводом и выводом с точки зрения типов доменов (таких как Order и Taco) или наборов этих типов доменов. В качестве напоминания рассмотрим следующий фрагмент из DesignTacoController, который вы написали в главе 6:

@RestController

@RequestMapping(path="/design", produces="application/json")

@CrossOrigin(origins="*")

public class DesignTacoController {

   ...

   @GetMapping("/recent")

   public Iterable recentTacos() {

      PageRequest page = PageRequest.of(

         0, 12, Sort.by("createdAt").descending());

      return tacoRepo.findAll(page).getContent();

   }

...

}

Как уже было ранее написано, контроллер latestTacos() обрабатывает HTTP-запросы GET для /design/recent, чтобы вернуть список недавно созданных тако. Более конкретно, он возвращает Iterable с типом Taco. Это в первую очередь потому, что это то, что возвращается из метода findAll() репозитьтлоия, или, точнее, из метода getContent() объекта Page, возвращаемого из findAll().

Это прекрасно работает, но Iterable не является реактивным типом. Вы не сможете применить к нему какие-либо реактивные операции, и при этом вы не сможете позволить фрэймворку использовать его как реактивный тип для разделения любой работы на несколько потоков. То, что вы хотели бы, чтобы recentTacos() возвращали Flux .

Простой, но несколько ограниченный вариант здесь - переписать recentTacos() для преобразования Iterable во Flux. А заодно избавимся от кода паджинации и заменим его вызовом метода take() у Flux:

@GetMapping("/recent")

public Flux recentTacos() {

   return Flux.fromIterable(tacoRepo.findAll()).take(12);

}

Используя Flux.fromIterable(), вы конвертируете Iterable в Flux. И теперь, когда вы работаете с Flux, вы можете использовать операцию take(), чтобы ограничить возвращаемый Flux максимум 12 объектами Taco. Код не только проще, он также имеет дело с реактивным Flux, а не простым Iterable.

До сих пор написание реактивного кода было выигрышным шагом. Но было бы еще лучше, если бы репозиторий давало вам Flux для начала, чтобы вам не нужно было выполнять преобразование. Если бы это было так, то recentTacos() можно было бы написать так:

@GetMapping("/recent")

public Flux recentTacos() {

   return tacoRepo.findAll().take(12);

}

Это даже лучше! В идеале реактивный контроллер будет вершиной стека, который является реактивным от начала до конца, включая контроллеры, репозитории, базу данных и любые службы, которые могут находиться между ними. Такой сквозной реактивный стек показан на рис. 11.3.

Рис. 11.3. Чтобы максимизировать преимущества реактивного веб-фрэймворка, она должна быть частью полного сквозного реактивного стека.

Такой end-to-end стек требует, чтобы репозиторий был написан так, чтобы он возвращал Flux вместо Iterable. Мы рассмотрим написание реактивных репозиториев в следующей главе, но вот краткий обзор того, как может выглядеть реактивный TacoRepository:

public interface TacoRepository