Введение в Spring Reactor

Введение в Spring Reactor

1. обзор

В этой быстрой статье мы познакомим вас с проектом Spring Reactor. Мы создадим реальный сценарий для реактивного, событийного приложения.

2. Основы Spring Reactor

2.1. Почему реактор?

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

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

2.2. Примеры сценариев

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

  • Служба уведомлений о крупных интернет-магазинах приложений, таких как Amazon

  • Огромные услуги по обработке транзакций банковского сектора

  • Торговля акциями, где цены на акции изменяются одновременно

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

3. Maven Зависимости

Давайте начнем использовать Spring Reactor, добавив следующую зависимость в нашpom.xml:


    io.projectreactor
    reactor-bus
    2.0.8.RELEASE

Вы можете проверить последнюю версию реактора-шины вCentral Maven Repository.

4. Создание демонстрационного приложения

Чтобы лучше понять преимущества подхода на основе реакторов,let’s look at a practical example.

Мы собираемся создать простое приложение для уведомлений, которое будет уведомлять пользователей по почте и SMS после того, как они завершат заказ в интернет-магазине.

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

Благодаря реактивному подходу система может быть более гибкой и лучше адаптироваться к сбоям или тайм-аутам в таких внешних системах, таких как SMS или почтовые серверы.

Давайте посмотрим на приложение - начнем с более традиционных аспектов и перейдем к более реактивным конструкциям.

4.1. Простой POJO

Во-первых, давайте создадим классPOJO для представления данных уведомления:

public class NotificationData {

    private long id;
    private String name;
    private String email;
    private String mobile;

    // getter and setter methods
}

4.2. Уровень обслуживания

Давайте теперь настроим простой сервисный слой:

public interface NotificationService {

    void initiateNotification(NotificationData notificationData)
      throws InterruptedException;

}

И реализация, имитирующая длительную операцию здесь:

@Service
public class NotificationServiceimpl implements NotificationService {

    @Override
    public void initiateNotification(NotificationData notificationData)
      throws InterruptedException {

      System.out.println("Notification service started for "
        + "Notification ID: " + notificationData.getId());

      Thread.sleep(5000);

      System.out.println("Notification service ended for "
        + "Notification ID: " + notificationData.getId());
    }
}

Обратите внимание: чтобы проиллюстрировать реальный сценарий отправки сообщений через шлюз SMS или шлюз электронной почты, мы намеренно вводим 5-секундную задержку в методеinitiateNotification наThread.sleep(5000).

И так, когда поток попадает в сервис - он будет заблокирован на 5 секунд.

4.3. Потребитель

Теперь перейдем к более реактивным аспектам нашего приложения и реализуем потребителя, которого мы затем сопоставим сreactor event bus:

@Service
public class NotificationConsumer implements
  Consumer> {

    @Autowired
    private NotificationService notificationService;

    @Override
    public void accept(Event notificationDataEvent) {
        NotificationData notificationData = notificationDataEvent.getData();

        try {
            notificationService.initiateNotification(notificationData);
        } catch (InterruptedException e) {
            // ignore
        }
    }
}

Как видите, потребитель просто реализует интерфейсConsumer<T> - с помощью одного методаaccept. Это простая реализация, которая запускает основную логику, как иa typical Spring listener.

4.4. Контроллер

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

Мы собираемся сделать это в простом контроллере:

@Controller
public class NotificationController {

    @Autowired
    private EventBus eventBus;

    @GetMapping("/startNotification/{param}")
    public void startNotification(@PathVariable Integer param) {
        for (int i = 0; i < param; i++) {
            NotificationData data = new NotificationData();
            data.setId(i);

            eventBus.notify("notificationConsumer", Event.wrap(data));

            System.out.println(
              "Notification " + i + ": notification task submitted successfully");
        }
    }
}

Это говорит само за себя - здесь мы отправляем события черезEventBus - используя уникальный ключ.

Итак, проще говоря - когда клиент нажимает на URL со значением параметра 10, через шину будет отправлено в общей сложности 10 событий.

4.5. Конфигурация Java

Мы почти закончили; давайте просто соберем все вместе с Java Config и создадим наше загрузочное приложение:

import static reactor.bus.selector.Selectors.$;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application implements CommandLineRunner {

    @Autowired
    private EventBus eventBus;

    @Autowired
    private NotificationConsumer notificationConsumer;

    @Bean
    Environment env() {
        return Environment.initializeIfEmpty().assignErrorJournal();
    }

    @Bean
    EventBus createEventBus(Environment env) {
        return EventBus.create(env, Environment.THREAD_POOL);
    }

    @Override
    public void run(String... args) throws Exception {
        eventBus.on($("notificationConsumer"), notificationConsumer);
    }

    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }
}

Именно здесь мы создаем bean-компонентEventBus через статический APIcreate вEventBus.

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

Если бы мы хотели немного больше контроля над шиной, мы также могли бы предоставить счетчик реализации:

EventBus evBus = EventBus.create(
  env,
  Environment.newDispatcher(
    REACTOR_THREAD_COUNT,REACTOR_THREAD_COUNT,
    DispatcherType.THREAD_POOL_EXECUTOR));

Далее - также обратите внимание, как мы здесь используем статический импорт атрибута$.

Эта функция предоставляет безопасный для типов механизм для включения констант (в нашем случае это атрибут $) в код без необходимости ссылаться на класс, который изначально определил поле.

Мы используем эту функцию в нашей реализации методаrun -where we’re registering our consumer to be triggered when the matching notification.

Это основано наa unique selector key, которое позволяет идентифицировать каждого потребителя.

5. Протестируйте приложение

После запуска сборки Maven теперь мы можем просто запуститьjava -jar name_of_the_application.jar для запуска приложения.

Теперь давайте создадим небольшой тестовый класс JUnit для тестирования приложения. Мы бы использовалиSpringJUnit4ClassRunner Spring Boot для создания тестового примера:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Application.class})
public class DataLoader {

    @Test
    public void exampleTest() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getForObject(
          "http://localhost:8080/startNotification/10", String.class);
    }
}

Теперь давайте запустим этот тестовый пример, чтобы протестировать приложение:

Notification 0: notification task submitted successfully
Notification 1: notification task submitted successfully
Notification 2: notification task submitted successfully
Notification 3: notification task submitted successfully
Notification 4: notification task submitted successfully
Notification 5: notification task submitted successfully
Notification 6: notification task submitted successfully
Notification 7: notification task submitted successfully
Notification 8: notification task submitted successfully
Notification 9: notification task submitted successfully
Notification service started for Notification ID: 1
Notification service started for Notification ID: 2
Notification service started for Notification ID: 3
Notification service started for Notification ID: 0
Notification service ended for Notification ID: 1
Notification service ended for Notification ID: 0
Notification service started for Notification ID: 4
Notification service ended for Notification ID: 3
Notification service ended for Notification ID: 2
Notification service started for Notification ID: 6
Notification service started for Notification ID: 5
Notification service started for Notification ID: 7
Notification service ended for Notification ID: 4
Notification service started for Notification ID: 8
Notification service ended for Notification ID: 6
Notification service ended for Notification ID: 5
Notification service started for Notification ID: 9
Notification service ended for Notification ID: 7
Notification service ended for Notification ID: 8
Notification service ended for Notification ID: 9

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

Учтите, что в нашем сценарии нет необходимости обрабатывать эти события в каком-либо порядке.

6. Заключение

В этом небольшом приложении мы определенно получаем увеличение пропускной способности вместе с более качественным приложением в целом.

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

Как всегда доступен исходный кодover on GitHub.