«Когда пару лет назад я впервые столкнулась с реактивным программированием, — рассказывает моя коллега Екатерина, — казалось, что это что‑то слишком сложное и академическое. Но чем больше работаешь с современными высоконагруженными системами, тем яснее становится, что без реактивного подхода сложно обеспечить высокую отзывчивость и масштабируемость».

Екатерина,

разработчик Java в Programming Store

Введение

Сегодня реактивные технологии уже перестали быть экзотикой. Netflix, Uber, Alibaba — все они активно используют реактивные стеки, чтобы выдерживать миллионы одновременных подключений. И если вы Java-разработчик, то знание WebFlux, Reactor или R2DBC становится не просто преимуществом, а необходимостью.

Представьте обычное современное веб-приложение: пользователи лайкают, отправляют сообщения, загружают файлы, получают пуши — всё одновременно и в реальном времени. На классических синхронных потоках такое приложение быстро сталкивается с ограничениями производительности. Именно в этом случае преимущества реактивного программирования проявляются наиболее явно.

Идеи реактивности появились ещё в 90-х, когда разработчики заговорили о потоках данных и наблюдаемых последовательностях. Но настоящий прорыв случился ближе к 2014 году. Netflix столкнулся с тем, что традиционные синхронные архитектуры просто не тянут миллионы пользователей, постоянные запросы, гигантскую нагрузку на сеть. Решение родилось внутри компании — библиотека RxJava, которая позже была открыта миру и получила широкое распространение в Java-сообществе.

Reactive Extensions (Rx)

Примерно в то же время Microsoft активно развивал Reactive Extensions (Rx) под .NET. Это подтвердило, что концепция универсальна и применима в любых экосистемах.

Реактивное программирование в Java — это не абстрактная концепция, а вполне конкретные технологии и стандарты.

Reactive Streams API сначала появился как отдельная спецификация (org.reactivestreams), а в Java 9 — java.util.concurrent.Flow, семантически эквивалентный её интерфейсам. Он определяет:

  • Publisher — источник (поставщик) данных,

  • Subscriber — потребитель данных,

  • Subscription — управление потоком,

  • Processor — компонент, который является одновременно подписчиком и издателем (то есть потребляет элементы типа T и выпускает элементы типа R).

Project Reactor — флагманская реализация от Pivotal (создателей Spring), включающая:

  • Mono представляет асинхронную последовательность с максимум одним элементом,

  • Flux — последовательность от 0 до N элементов.

Spring WebFlux — реактивный веб-фреймворк, выпущенный в Spring 5 (2017):

  • работает по умолчанию на Netty,

  • обеспечивает лучшую масштабируемость и меньшие затраты потоков и памяти в I/O-bound сценариях по сравнению с классическим Spring MVC.

Таким образом, Reactive Streams — это спецификация, то есть набор интерфейсов, которые описывают, как именно должны взаимодействовать компоненты в реактивной системе. Project Reactor — это уже конкретная реализация этих интерфейсов. Его классы Flux и Mono — это, по сути, Publisher, но с мощным набором операторов (map, flatMap, filter, merge и т. д.), которые позволяют легко описывать асинхронные цепочки обработки данных. А Spring WebFlux — это надстройка над Reactor, которая применяет эти принципы в веб-контексте. Она позволяет строить неблокирующие REST-контроллеры, маршруты и обработчики запросов, используя Mono и Flux как стандартные типы возвращаемых значений.

Современная реактивная экосистема Java включает:

  • R2DBC — реактивный доступ к реляционным БД,

  • Reactive MongoDB/Cassandra драйверы,

  • RSocket — реактивный протокол для микросервисов,

  • Micrometer — продвинутые метрики для мониторинга.

Если сравнивать, то традиционная модель похожа на службу такси: одна машина — один пассажир, и, если где-то пробка, всё стоит. Реактивный подход ближе к метро: один состав перевозит тысячи людей одновременно, движение не останавливается, ресурсы используются эффективно.

Но реактивное программирование не волшебная таблетка. Это просто естественный шаг вперёд, когда классические блокирующие подходы перестают справляться. И самое приятное — начать можно постепенно. Вы можете внедрять реактивные компоненты точечно, не переписывая всё приложение с нуля.

Команда Spring отмечает, что WebFlux способен обрабатывать больше одновременных соединений при меньших затратах ресурсов по сравнению с классическим Spring MVC в I/O-bound сценариях. Ниже я покажу, как шаг за шагом перейти к реактивному подходу, не потеряв устойчивости и простоты сопровождения кода.

Reactive Manifesto: четыре принципа современных систем

Reactive Manifesto — это не сухая спецификация, а, скорее, набор идей о том, как строить живые, адаптивные системы. Представьте, что вы создаёте не просто приложение, а организм, который должен спокойно переносить стресс, меняться под давлением и оставаться в форме. Именно к этому и сводятся четыре базовых принципа реактивного подхода.

Ниже приведены краткие примеры кода, иллюстрирующие работу реактивного программирования. Они не являются универсальным стандартом и в реальных системах требуют дополнительной логики: логирования ошибок, метрик, обработки отказов и применения шаблонов, таких как bulkhead, circuit breaker, fallback, back‑pressure, partitioning и др., в зависимости от потребностей системы.

Responsive (отзывчивость) — основа пользовательского опыта

Любая система должна отвечать быстро и предсказуемо — независимо от того, что происходит внутри. Пользователь не должен долго ожидать ответа и думать, все ли в порядке. Даже под нагрузкой система должна сохранять ощущение плавности и контроля.

// Традиционный подход может "зависнуть"
 public String loadUserDataBlocking(String userId) {
     // Может блокировать поток на неопределенное время
     return database.query("SELECT * FROM users WHERE id = ?", userId);
 }
// Реактивный подход гарантирует ответ
public Mono<String> loadUserDataReactive(String userId) {
    return userRepository.findById(userId) // userRepository должен быть реактивный
        .timeout(Duration.ofSeconds(3))      // Гарантия максимального времени ответа
        .onErrorReturn("Пользователь не доступен"); // Всегда возвращаем результат
}

Resilient (устойчивость) — искусство оставаться на плаву

Даже лучшие системы иногда ломаются, и это нормально. Главное, чтобы поломка в одном месте не тянула за собой всё остальное.
Реактивная архитектура как раз и помогает локализовать сбои, изолировать проблемы и восстанавливаться без вмешательства человека.

public Mono<Order> processOrder(Order order) {
     return inventoryService.reserveItems(order) // если тут есть блокирующие вызовы то нужно добавлять .subscribeOn(Schedulers.boundedElastic()
         .transformDeferred(circuitBreaker::run) // Защита от повторяющихся сбоев
         .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))) // 3 попытки с растущей задержкой
         .timeout(Duration.ofSeconds(30))
         .onErrorResume(TimeoutException.class, e -> {
             // Переход на упрощенную логику при таймауте
             return processOrderWithLimitedFunctionality(order);
         })
         .onErrorResume(ServiceUnavailableException.class, e -> {
             return processOrderWithoutReservation(order);
         })
         .onErrorReturn(createFallbackOrder(order));
 }

Если проводить аналогию, это напоминает торговый центр: отключился эскалатор — включились лифты; пропало электричество — зажглось аварийное освещение. Система не останавливается, а просто переходит в другой режим.

Elastic (эластичность) — гибкость под нагрузкой

Нагрузки растут, трафик скачет, и система должна реагировать на это сама, без паники. Эластичность — это способность приложения масштабироваться туда, где нужно, и не держать лишние ресурсы, когда всё спокойно.

@Bean
 public Scheduler elasticScheduler() {
     return Schedulers.newBoundedElastic(
         10,     //  лимит расширения при пиковой нагрузке
         1000,   //  буфер для задач при превышении лимита потоков
         "elastic-pool",
         60,    // Потоки, которые простаивают более 60 секунд, автоматически удаляются
         true);

}
public Flux<String> processBatchReactive(List<String> items) {
     return Flux.fromIterable(items)
         .flatMap(item -> 
                        Mono.fromCallable(() -> processItem(item)) // тут processItem - блокирующая операция, если тут будет реактивный метод, то subscribeOn не нужен
                                .subscribeOn(elasticScheduler())
                 // Если processItem выполняется дольше 30 секунд - операция прерывается
                 .timeout(Duration.ofSeconds(30))
                 // При ошибке будет 3 попытки с задержкой
                 .retryWhen(Retry.backoff(3, Duration.ofMillis(100)))
                 .name("processItem")
                 .metrics() // метрики нужно настраивать дополнительно
         , 
               10  // Максимум 10 одновременных операций processItem()
            )
         // Если вся обработка списка занимает больше 5 минут - прерываем
         .timeout(Duration.ofMinutes(5));
 }

Эластичность работает на всех уровнях: Kubernetes поднимает больше подов при пике трафика, а Reactor эффективно распределяет нагрузку уже внутри каждого экземпляра.

Message-Driven (ориентированность на сообщения) — основа коммуникации

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

// сервис для отправки заказов в брокер сообщений
@Service
public class ReactiveOrderService {
    private final StreamBridge streamBridge;

    public ReactiveOrderService(StreamBridge streamBridge) {
        this.streamBridge = streamBridge;
    }

    public Mono<Void> processOrder(Order order) {
    return Mono.fromCallable(() -> streamBridge.send("orders-out-0", order))
                  .subscribeOn(Schedulers.boundedElastic())
                  .doOnSuccess(sent -> log.info("✅ Заказ отправлен: {}", order.getId()))
                  .then();
}
// обработчик входящих сообщений
@Component
public class OrderMessageHandler {
    private final ReactiveOrderService orderService;

    public OrderMessageHandler(ReactiveOrderService orderService) {
        this.orderService = orderService;
    }

    @Bean
    public Consumer<Flux<Order>> orderProcessor() {
        return flux -> flux
            .flatMap(order -> orderService.processOrder(order));
    }
}

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

Эти принципы не работают по отдельности:

  • Message-Driven даёт основу для Elastic масштабирования,

  • Resilient помогает системе оставаться Responsive даже при сбоях,

  • а Elastic характер поддерживает устойчивость, когда нагрузка скачет.

В итоге система не просто работает: она живёт и предсказуемо себя ведёт в условиях постоянной нагрузки. Это и есть суть реактивного подхода: не бороться с хаосом, а научиться с ним сосуществовать.

Когда выбирать реактивный и традиционный подход?

Реактивный подход:

Приложение

Почему подходит

Пример

Чат, мессенджер

Тысячи сообщений в реальном времени

Telegram

Торговая платформа

Мгновенные обновления цен

Биржевые терминалы

Стриминговый сервис

Потоковая передача видео

Netflix

Игровой сервис

Многопользовательские игры онлайн

Игровые серверы

Мониторинг систем

Постоянный поток метрик

Grafana, дашборды

Традиционный подход:

Приложение

Почему подходит

Пример

Интернет-магазин

Синхронные транзакции с гарантированной согласованностью данных

Amazon, OZON

Банковское приложение

Сложные транзакции со строгой согласованностью

Мобильный банк

Корпоративный портал

Документооборот, CRM

Аналитические отчёты

Сложные расчёты, статистика

Excel

Реактивный подход, если нужно:

  • масштабирование: >10000 пользователей онлайн,

  • реальное время: данные обновляются каждую секунду,

  • потоковые данные: видео, аудио, события IoT,

  • позволяет эффективнее использовать серверные ресурсы и масштабироваться при высокой нагрузке

Традиционный подход, если:

  • простота: команда из 1-3 разработчиков,

  • логика больших вычислений и CPU-bound,

  • стандартный CRUD: формы, таблицы, отчёты,

  • сжатые сроки: нужно быстро выпустить MVP.

Все приведённые выше критерии не являются строгим руководством к действию. Они, скорее, служат поводом задуматься и проанализировать: действительно ли в вашем случае оправдан реактивный подход. То, что отлично работает в одном приложении, может оказаться неоправданно сложным или неэффективным в другом. Всё зависит от конкретных задач и контекста системы. Задайте себе несколько вопросов, которые помогут в анализе:

  1. Какова основная нагрузка — I/O-bound или CPU-bound?

  2. Требуется ли работа в реальном времени и минимальная задержка отклика?

  3. Есть ли высокая параллельность, множество одновременных соединений или непредсказуемые пики нагрузки?

  4. Насколько команда знакома с реактивным подходом?

  5. Поддерживают ли используемые библиотеки и инфраструктура неблокирующий режим?

Практическое начало: первые шаги с Reactor. Базовые операции с Mono и Flux

Когда использовать MONO (один результат):

public class MonoUseCases {
     
     // поиск по ID - всегда один объект или null
     Mono<User> findUserById(String userId) {
         return userRepository.findById(userId); // предполагается, что userRepository реактивный
     }
     
     // создание ресурса - возвращаем созданный объект
     Mono<Order> createOrder(OrderRequest request) {
         return orderRepository.save(request.toOrder());
     }
     
     // аутентификация - возвращаем токен или ошибку
     Mono<AuthToken> login(String email, String password) {
         return authService.authenticate(email, password);
     }
     
     // валидация - успех или ошибка
     Mono<Void> validateEmail(String email) {
         return emailValidator.isValid(email) 
             ? Mono.empty() 
             : Mono.error(new InvalidEmailException());
     }
     
 }

Когда использовать FLUX (поток результатов):

 //  список элементов - много объектов
     Flux<Product> getAllProducts() {
         return productRepository.findAll();
     }
     
     // поиск с фильтрацией - несколько результатов
     Flux<User> findUsersByCity(String city) {
         return userRepository.findByCity(city);
     }
     
     // реальное время - поток событий
     @GetMapping(value = "/notifications", 
                 produces = MediaType.TEXT_EVENT_STREAM_VALUE)
     Flux<Notification> streamNotifications(String userId) {
         return notificationService.getUserNotifications(userId)
             .doOnCancel(() -> log.info("Клиент отключен")); // Обработка отключения
     }
     
     // IoT данные - непрерывный поток с датчиков
     Flux<SensorData> streamSensorData(String deviceId) {
     return sensorService.subscribeToDevice(deviceId)
         .sample(Duration.ofSeconds(1))     // Дросселирование - 1 значение в секунду
         .onBackpressureLatest()            // Только последнее значение при перегрузке
         .doOnSubscribe(sub -> log.info("Подписан на устройство: {}", deviceId))
         .doOnComplete(() -> log.info("Устройство {} стрим завершен", deviceId));
 }
     
     // аудит и логи - поток событий системы
     Flux<AuditEvent> getAuditLog(LocalDateTime from, LocalDateTime to) {
         return auditRepository.findByTimestampBetween(from, to); 

    }

Разница между традиционным и реактивным подходом в поведении при нагрузке

Традиционный подход (проблемы):

//  блокирующий подход
 @GetMapping("/users/{id}")
 public User findUserById(String userId) {
     // Каждый запрос занимает один поток на всё время выполнения
     User user = userRepository.findById(userId); // Поток БЛОКИРОВАН на 200ms
     return user;
     // Поток освобождается только после полного выполнения
 }
 
 // ПРИ 1000 одновременных запросов:
 // 1000 потоков × 200ms = 200 секунд блокировки!
 // Сервер "захлёбывается" - кончаются потоки

Реактивный подход (решение):

// неблокирующий подход  
 @GetMapping("/users/{id}")
 public Mono<User> findUserById(String userId) {
     return userRepository.findById(userId); // реактивный userRepository
     // Поток НЕ блокируется - сразу освобождается!
     // Запрос "подписывается" на результат и ждёт его асинхронно
 }
 
 // ПРИ 1000 одновременных запросов:
 // 1 поток может обработать 10000+ таких запросов!
 // Сервер использует ресурсы эффективно

Традиционный подход (проблемы):

// Вся коллекция загружается в память сразу
 @GetMapping("/products")
 public List<Product> getAllProducts() {
     // Все продукты загружаются в память одновременно
     List<Product> products = productRepository.findAll(); 
     // Блокируем поток до полной загрузки всех данных
     return products;
     // При 1,000,000 товаров: 1GB памяти + блокировка на 2+ секунды
 }

Реактивный подход (решение):

//  Данные стримятся постепенно
 @GetMapping(value = "/products", produces = MediaType.APPLICATION_NDJSON_VALUE)
 public Flux<Product> getAllProductsReactive() {
     return productRepository.findAll(); реактивный productRepository
     // Данные отправляются по мере готовности
     // Поток освобождается сразу, клиент получает данные постепенно
     // При 1,000,000 товаров: 1MB буфер + неблокирующая обработка
 }

Ошибки при реактивном программировании

Далее приведу три самых частых случая, когда код вроде бы реактивный, но фактически работает синхронно, блокирует потоки или теряет данные.

1. Использование .block() и .subscribe() не там, где нужно.

Вызовы .block() или .subscribe() ломают асинхронность, если использовать их в контроллерах или сервисах.

@GetMapping("/users/{id}")
public User getUser(String id) {
// Ошибка: блокируем поток до получения результата
    return userService.findById(id).block(); 
}
  • .block() заставляет поток ждать результат — теряется вся неблокирующая модель.

  • под высокой нагрузкой сервер быстро испытывает дефицит ресурсов, поскольку каждый запрос блокирует поток выполнения.

Фреймворк WebFlux сам подписывается на поток. Нужно просто вернуть Mono или Flux.

@GetMapping("/users/{id}")
 public Mono<User> getUser(String id) {

// Реактивно: поток сразу освобождается
     return userService.findById(id);
 }

Ошибка: запускаем поток внутри контроллера

@GetMapping("/orders")
 public void process() {
     orderService.getOrders()
         .subscribe(order -> log.info("Order: {}", order));
 }

.subscribe() необходим, когда вы создаете поток и берете на себя управление его жизненным циклом. Иногда это может понадобиться.

@Bean
 @Scheduled(fixedRate = 5000)
 public Disposable backgroundCleanup() {
     // Самостоятельно создаем и управляем потоком
     return userService.cleanupExpiredSessions()
         .subscribeOn(Schedulers.boundedElastic())
         .subscribe(
             count -> log.info("Cleaned up {} sessions", count),
             error -> log.error("Cleanup failed", error)
         );
 }

 

@Component
 public class OrderMessageProcessor {
     private Disposable subscription;
     
     @EventListener(ApplicationReadyEvent.class)
     public void startProcessing() {
         // Самостоятельно создаем поток из Kafka
         this.subscription = kafkaReceiver.receive()
             .flatMap(record -> 
                 orderService.process(record.value())
                     .doOnSuccess(result -> record.receiverOffset().acknowledge())
                     .subscribeOn(Schedulers.boundedElastic())
             )
             .subscribe(
                 result -> log.debug("Processed order"),
                 error -> log.error("Order processing failed", error)
             );
     }
     
     @PreDestroy
     public void stopProcessing() {
         if (subscription != null) {
             subscription.dispose();
         }
     }
 }

2. Блокирующие вызовы внутри реактивных потоков.

Иногда разработчик вроде бы пишет на Flux и Mono, но внутри всё равно вызывает блокирующий код — базы, API, файловые операции.

public Flux<User> findAllUsers() {
     return Flux.fromIterable(userRepository.findAll()); // блокирующий JPA вызов
 }

Такой код формально реактивный, но по факту «тормозной». Поток Reactor ожидает завершения findAll(), пока другие операции простаивают.

Если блокирующий вызов избежать нельзя, нужно вынести его на отдельный пул потоков с помощью Schedulers.boundedElastic():

public Flux<User> findAllUsers() {
     return Mono.fromCallable(() -> userRepository.findAll()) // Безопасный вызов
         .subscribeOn(Schedulers.boundedElastic())           // Вынос в отдельный пул потоков
         .flatMapMany(Flux::fromIterable);                   // Преобразование в поток
 }

boundedElastic — это динамический пул потоков с ограничением по количеству активных задач, оптимальный для кратковременных блокирующих операций (I/O, файловые операции, JDBC). Но он не предназначен для тяжёлых вычислений. Для этого лучше использовать Schedulers.parallel().

Проверяйте библиотеки. Если они не поддерживают Reactive Streams (например, JPA или старые HTTP-клиенты), не вызывайте их напрямую в реактивных цепочках.

3. Потеря управления backpressure (перегрузка потока).

Многие новички даже не подозревают о существовании backpressure — механизма контроля скорости потока. Без него реактивное приложение может «утонуть» в собственных данных: производитель шлёт миллионы событий, а потребитель не успевает обрабатывать.

Flux.interval(Duration.ofMillis(1))
     .map(this::process)
     .subscribe(); // Поток быстро переполнится

На первый взгляд, Flux.interval() безопасен, но если внутри цепочки тяжёлая обработка (например, flatMap без ограничения), поток событий может накапливаться быстрее, чем обрабатывается, отсюда и необходимость onBackpressure.

Используйте встроенные стратегии Reactor:

  • .onBackpressureBuffer() — временно хранить элементы в буфере,

  • .onBackpressureDrop() — отбрасывать лишние,

  • .onBackpressureLatest() — оставлять только последние.

Flux.interval(Duration.ofMillis(1))
     .onBackpressureLatest()              // ограничиваем поток
     .flatMap(this::process)
     .subscribe();

Backpressure — это как предохранитель на конвейере. Если рабочий не успевает, система притормаживает подачу деталей, а не засыпает его тысячами.

Посмотрим на реальном примере одного метода получения заказов клиента, как будет выглядеть код, если мы перейдём от традиционного подхода к реактивному.

Традиционный код.

Такой код работает, но блокирует потоки: каждый запрос ждёт завершения обращения к БД. Под большой нагрузкой это быстро становится узким местом.

@RestController
 @RequiredArgsConstructor
 @Slf4j
 public class OrderController {
 
     private final ClientOrderFacade clientOrderFacade;
     
     @GetMapping
     public List<Order> getAllOrders(String personId) {
         return clientOrderFacade.getAllOrders(personId);
     }
 }

@Component
 @RequiredArgsConstructor
 @Slf4j
 public class ClientOrderFacade {
 private final OrderService orderService;

private final PersonService personService;
 
     @Transactional
     public List<Order> getAllOrders(String personId) {
         Person person = personService.findById(personId);
         return orderService.findAllByPerson(person)
             .stream()
             .sorted(Comparator.comparing(Order::getCreated).reversed())
             .toList();
     }
 }

@Service
 @RequiredArgsConstructor
 @Slf4j
 public class OrderServiceImpl implements OrderService {
     private final OrderRepository orderRepository;
     
     @Override
     public Set<Order> findAllByPerson(Person person) {
         return orderRepository.findAllByPerson(person);
     }
 }

@Repository
 public interface OrderRepository extends JpaRepository<Order, Long> {
     Set<Order> findAllByPerson(Person person);
 }

Реактивная версия (Spring WebFlux + Reactor).

В pom.xml нужно добавить:

<!-- Spring WebFlux -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-webflux</artifactId>
 </dependency>
 
 <!-- Для реактивного доступа к БД -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-r2dbc</artifactId>
 </dependency>
 
 <!-- Драйвер для PostgreSQL -->
 <dependency>
     <groupId>io.r2dbc</groupId>
     <artifactId>r2dbc-postgresql</artifactId>
 </dependency>
 
 

@RestController
 @RequiredArgsConstructor
 @Slf4j
 public class OrderController {
 
     private final ClientOrderFacade clientOrderFacade;
 
     @GetMapping
     public Flux<Order> getAllOrders(@RequestParam String personId) {
         log.info("Поиск заказов пользователя {}", personId );
         return clientOrderFacade.getAllOrders(personId);
     }
 }

List<Order> заменён на Flux<Order>. Контроллер теперь возвращает поток данных, который WebFlux отдаёт клиенту по мере готовности — без блокировки.

@Component
 @RequiredArgsConstructor
 @Slf4j
 public class ClientOrderFacade {
 
     private final OrderService orderService;
     private final PersonService personService;
 
     public Flux<Order> getAllOrders(String personId) {
         return personService.findById(personId)
             .switchIfEmpty(Mono.error(new RuntimeException("Пользователь не найден")))
             .flatMapMany(person -> orderService.findAllByPerson(person.getId()));
     }
 }

 

@Service
 @RequiredArgsConstructor
 @Slf4j
 public class OrderService {
 
     private final OrderRepository orderRepository;
 
     public Flux<Order> findAllByPerson(String personId) {
         return orderRepository.findAllByPersonId(personId);
     }
 }

@Repository
 public interface OrderRepository extends R2dbcRepository<Order, Long> {
 
     @Query("SELECT * FROM orders WHERE person_id = :personId ORDER BY created DESC")
     Flux<Order> findAllByPersonId(String personId);

}

Выводы

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

Оно не требует переписывать всё с нуля: начните с одного контроллера, одного реактивного потока, одного Flux. Постепенно вы увидите, как данные обрабатываются асинхронно и не блокирующе, отдавая элементы клиенту по мере их готовности. Реактивная модель с поддержкой backpressure гарантирует, что система не перегрузит ресурсы при большом количестве запросов, а серверные потоки используются эффективно. Такой подход превращает приложение в реактивную систему, которая масштабируется под нагрузку и реагирует на события в реальном времени, вместо того чтобы блокировать потоки и ожидать завершения каждого запроса

Комментарии (1)


  1. kmatveev
    17.11.2025 06:52

    Чем плохи такие gpt-сгенерированные статьи, так это тем, что в начале идёт реклама и нахваливание, а потом будто начинается техническая часть, но все примеры какие-то обрезанные. Взять вот самый первый из реактивных примеров, loadUserDataReactive(). Из него непонятно, что такое возвращает userRepository и откуда его такой взять, чтобы он это возвращал (не говоря уже о том, что userRepository должен возвращать Mono<User> , а не Mono<String> , и вся эта хрень рассыпется). И таких бессмысленных вещей тут немало. Не надо таких статей, они не помогают.