Gymterview
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% сценариев.