Top.Mail.Ru
Full-time, 5/2
Формат: удаленный
Вакансия «1С-программист»

Реактивное программирование в Java: от теории к практике

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

Введение

Сегодня реактивные технологии уже перестали быть экзотикой. 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&lt;User> findUserById(String userId) {
         return userRepository.findById(userId); // предполагается, что userRepository реактивный
     }
     
     // создание ресурса - возвращаем созданный объект
     Mono&lt;Order> createOrder(OrderRequest request) {
         return orderRepository.save(request.toOrder());
     }
     
     // аутентификация - возвращаем токен или ошибку
     Mono&lt;AuthToken> login(String email, String password) {
         return authService.authenticate(email, password);
     }
     
     // валидация - успех или ошибка
     Mono&lt;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&lt;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 гарантирует, что система не перегрузит ресурсы при большом количестве запросов, а серверные потоки используются эффективно. Такой подход превращает приложение в реактивную систему, которая масштабируется под нагрузку и реагирует на события в реальном времени, вместо того чтобы блокировать потоки и ожидать завершения каждого запроса

Comments

So empty here ... leave a comment!

Добавить комментарий

Sidebar