senior
Реактивный vs императивный подход — когда что использовать?
Выбор между реактивным и императивным подходом — архитектурное решение, зависящее от требований к конкурентности, стека технологий и опыта команды.
Сравнение подходов
| Критерий | Императивный | Реактивный |
|---|---|---|
| Читаемость | Высокая, линейный код | Ниже, цепочки операторов |
| Отладка | Простая, понятный стектрейс | Сложная, длинные трейсы |
| Порог входа | Низкий | Высокий |
| Throughput при I/O | Ограничен пулом потоков | Высокий (event-loop) |
| Потребление памяти | ~1 MB на поток | Минимальное |
| Streaming | Ограниченный | Нативный |
| Экосистема | Полная (JPA, JDBC, все библиотеки) | Ограниченная (R2DBC, WebClient) |
Сравнение кода: императивный vs реактивный vs Virtual Threads
// Императивный (Spring MVC)
@GetMapping("/orders/{id}")
public OrderDto getOrder(@PathVariable Long id) {
Order order = orderRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Order not found"));
User user = userService.getUser(order.getUserId());
List<Item> items = itemService.getItems(order.getId());
return OrderDto.from(order, user, items);
}
// Реактивный (Spring WebFlux)
@GetMapping("/orders/{id}")
public Mono<OrderDto> getOrder(@PathVariable Long id) {
return orderRepository.findById(id)
.switchIfEmpty(Mono.error(new NotFoundException("Order not found")))
.flatMap(order -> Mono.zip(
userService.getUser(order.getUserId()),
itemService.getItems(order.getId()).collectList(),
(user, items) -> OrderDto.from(order, user, items)
));
}
// Virtual Threads (Java 21+ Spring MVC)
// application.yml: spring.threads.virtual.enabled=true
@GetMapping("/orders/{id}")
public OrderDto getOrder(@PathVariable Long id) {
// Тот же блокирующий код, но на виртуальных потоках
Order order = orderRepository.findById(id).orElseThrow();
User user = userService.getUser(order.getUserId());
List<Item> items = itemService.getItems(order.getId());
return OrderDto.from(order, user, items);
}
Когда выбирать реактивный подход
- Streaming-сценарии (SSE, WebSocket, бесконечные потоки данных)
- Уже реактивный стек (R2DBC, reactive MongoDB, reactive Kafka)
- API Gateway / BFF с высокой конкурентностью
- Сложная оркестрация асинхронных вызовов с backpressure
Когда выбирать императивный подход
- Типичные CRUD-приложения
- Работа с JPA/Hibernate, блокирующими библиотеками
- Команда без опыта реактивного программирования
- Java 21+ с Virtual Threads доступна
Частые ошибки
- «Реактивный = быстрый» — реактивный подход не ускоряет отдельный запрос, он повышает throughput при высокой конкурентности
- Частичная реактивность — один блокирующий вызов в реактивной цепочке уничтожает все преимущества
- Переход на WebFlux без реактивной БД — JDBC через
boundedElastic— компромисс, а не решение - Выбор технологии по моде — технический долг от неоправданного усложнения стоит дорого
Как используется в 2026
- Чёткое разделение: WebFlux — для streaming и edge-сервисов, MVC + Virtual Threads — для бизнес-логики
- Spring Boot 3.2+:
spring.threads.virtual.enabled=trueпереключает MVC на Virtual Threads одной строкой - Прагматичный тренд: «используй реактивность только там, где она даёт измеримое преимущество»
- Можно комбинировать: MVC-контроллеры + WebClient для исходящих запросов
На собеседовании: это вопрос senior-уровня — интервьюер ожидает аргументированный ответ с trade-offs, а не «WebFlux лучше». Назовите три сценария для реактивности и три для императивного подхода. Частая ошибка — не упомянуть Virtual Threads как третий путь, который в 2026 покрывает 80% сценариев.