@RestController
@RequestMapping(path="/design", produces="application/json")
@CrossOrigin(origins="*")
public class DesignTacoController {
...
@GetMapping("/recent")
public Iterable<Taco> 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 <Taco>.
Простой, но несколько ограниченный вариант здесь - переписать recentTacos() для преобразования Iterable во Flux. А заодно избавимся от кода паджинации и заменим его вызовом метода take() у Flux:
@GetMapping("/recent")
public Flux<Taco> recentTacos() {
return Flux.fromIterable(tacoRepo.findAll()).take(12);
}
Используя Flux.fromIterable(), вы конвертируете Iterable<Taco> в Flux<Taco>. И теперь, когда вы работаете с Flux, вы можете использовать операцию take(), чтобы ограничить возвращаемый Flux максимум 12 объектами Taco. Код не только проще, он также имеет дело с реактивным Flux, а не простым Iterable.
До сих пор написание реактивного кода было выигрышным шагом. Но было бы еще лучше, если бы репозиторий давало вам Flux для начала, чтобы вам не нужно было выполнять преобразование. Если бы это было так, то recentTacos() можно было бы написать так:
@GetMapping("/recent")
public Flux<Taco> recentTacos() {
return tacoRepo.findAll().take(12);
}
Это даже лучше! В идеале реактивный контроллер будет вершиной стека, который является реактивным от начала до конца, включая контроллеры, репозитории, базу данных и любые службы, которые могут находиться между ними. Такой сквозной реактивный стек показан на рис. 11.3.
Рис. 11.3. Чтобы максимизировать преимущества реактивного веб-фрэймворка, она должна быть частью полного сквозного реактивного стека.
Такой end-to-end стек требует, чтобы репозиторий был написан так, чтобы он возвращал Flux вместо Iterable. Мы рассмотрим написание реактивных репозиториев в следующей главе, но вот краткий обзор того, как может выглядеть реактивный TacoRepository:
public interface TacoRepository
extends ReactiveCrudRepository<Taco, Long> {
}
Однако наиболее важно отметить, что помимо работы с Flux вместо Iterable,, а также того, как вы получаете этот Flux, программная модель для определения реактивного контроллера WebFlux ничем не отличается от нереактивного контроллера Spring MVC. Оба аннотируются с помощью @RestController и высокоуровневого @RequestMapping на уровне класса. И оба имеют функции обработки запросов, которые аннотируются с помощью @GetMapping на уровне метода. Разница только в том, какой тип возвращают методы обработчика.
Еще одно важное замечание заключается в том, что, хотя вы получаете Flux<Taco> из репозитория, вы можете вернуть его без вызова subscribe(). Действительно, фреймворк вызовет для вас метод subscribe(). Это означает, что при обработке запроса /design/recent будет вызван метод recentTacos(), который вернется до того, как данные будут даже получены из базы данных!
ВОЗВРАЩЕНИЕ ОДИНОЧНЫХ ЗНАЧЕНИЙ
В качестве другого примера рассмотрим метод tacoById() из DesignTacoController, как он был написан в главе 6:
@GetMapping("/{id}")
public Taco tacoById(@PathVariable("id") Long id) {
Optional<Taco> optTaco = tacoRepo.findById(id);
if (optTaco.isPresent()) {
return optTaco.get();
}
return null;
}
Здесь этот метод обрабатывает запросы GET для /design/{id} и возвращает одиночный объект Taco. Поскольку findById() репозитория возвращает Optional, вам пришлось написать какой-то неуклюжий код, чтобы справиться с этим. Но предположим на минуту, что findById() возвращает Mono<Taco> вместо Optional<Taco>. В этом случае вы можете переписать tacoById(), чтобы выглядело следующим образом:
@GetMapping("/{id}")
public Mono<Taco> tacoById(@PathVariable("id") Long id) {
return tacoRepo.findById(id);
}
Вау! Это намного проще. Однако более важно то, что, возвращая Mono<Taco> вместо Taco, вы позволяете Spring WebFlux обрабатывать ответ реагирующим образом. Следовательно, ваш API будет лучше масштабироваться в ответ на большие нагрузки.
РАБОТА С ТИПАМИ RXJAVA
Стоит отметить, что хотя типы Reactor, такие как Flux и Mono, являются естественным выбором при работе с Spring WebFlux, вы также можете выбрать работу с типами RxJava, такими как Observable и Single. Например, предположим, что между DesignTacoController и внутренним репозиторием находится служба, которая работает в терминах типов RxJava. В этом случае метод recentTacos() может быть написан так: