Введение: RxJava, Reactor, причины миграции

RxJava и Spring Reactor - это библиотеки, реализующие спецификацию Reactive Streams, которая позволяет разработчикам создавать асинхронные и основанные на событиях программы через потоки данных / событий. Спецификация Reactive Streams добавляет методы, которые обеспечивают функциональный стиль программирования для этих потоков и автоматически обрабатывают масштабируемую многопоточность и параллелизм.

RxJava была одной из первых Java-реализаций Reactive библиотеки и одной из наиболее распространенных на сегодняшний день. RxJava фокусируется на постоянной поддержке и обратной совместимости для более ранних версий Java, что позволяет использовать его для разработки под Android.

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

Spring Reactor не поддерживает более ранние версии Java, что делает его неприменимым для мобильной разработки. Однако Spring Reactor нацелен на то, чтобы быть более эффективным и современным для других типов разработки. В результате даже автор книги по разработке RxJava утверждал, что Reactor - часто лучший выбор, если вам не нужна поддержка Android.

Многие компании приняли RxJava несколько лет назад из-за его производительности и встроенных мобильных возможностей. Однако в последнее время большая поддержка и производительность библиотеки Spring Reactor заставили инженеров задуматься, следует ли им переходить с RxJava. Однако миграция такой ключевой библиотеки представляет собой масштабное мероприятие: инженеры особенно обеспокоены тем, сколько будет стоить переключение.

Как оказалось, RxJava довольно недавно обновился с RxJava 1 до RxJava 2, чтобы реализовать спецификацию Reactive Streams, которая требует подобной масштабной миграции; это дополнительный стимул для инженеров перенести свои приложения Reactive в Reactor для приложений, отличных от Android.

В результате увеличения объема интеграции, повышения производительности и эквивалентного количества усилий, необходимых для обновления до RxJava 2 по сравнению с переходом на Spring Reactor, мы считаем, что многие кодовые базы будут проходить процесс миграции с RxJava 1.8 на Spring Reactor. 3, и этот пост призван помочь инженерам в этом процессе, выделив первые шаги, необходимые для выполнения такой миграции.

Миграция примитивного типа

Хорошей новостью является то, что Spring Reactor реализует только два основных типа данных, тогда как RxJava 2 расширяется до пяти. Теоретически это должно упростить переход на Reactor. Есть два типа:

  • Flux: этот тип больше всего похож на Observable в RxJava 1.8, с некоторыми отличиями, которые почти идентичны Flowables в RxJava 2. Flux похож на Observable, для которого включено обратное давление. Это Reactive Streams Publisher с реактивными операторами, который испускает от 0 до N элементов, а затем завершается успешно или с ошибкой. [1]
  • Mono: этот тип больше всего похож на Single в RxJava 1.8. Это Reactive Streams Publisher с базовыми реактивными операторами, который успешно завершает работу, создавая элемент или с ошибкой. [2]

В общем, потоки данных можно переключать (более или менее) напрямую с Observable / Single на Flux / Mono, при этом главное обращать внимание на то, чтобы быть Backpressure. В отличие от Observable, который буферизует элементы, которые не могут быть немедленно обработаны, если для Flux требуется обратное давление (издатель, который игнорирует запрос, например, Flux.interval) и не применяется стратегия обратного давления, тогда выполнение приведет к исключению OverflowException, MissingBackpressureException или OutOfMemoryError. Простым решением этой проблемы является определение onBackpressureBuffer (буферы для элементов, которые в настоящее время не могут быть обработаны), onBackpressureDrop (игнорирование элементов, которые в настоящее время не могут быть обработаны) или использование других стратегий внутри ваш реактивный поток.

Создание реактивных потоков

Если выполнить полную миграцию слишком сложно, рассмотрите возможность прямого преобразования реактивных потоков в качестве временной меры. Имейте в виду, что это до некоторой степени лейкопластырь, и он будет более эффективным, если вы сможете создать свой Reactive Stream как Flux с нуля.

Это так же просто, как использование оператора Flux.from для чего угодно, от списка до RxJava 1 Observable / RxJava 2 Flowable. Аналогичным образом вы также можете использовать Mono.just для отдельных элементов или одиночных элементов RxJava.

Планировщики

В приложениях, обслуживающих большие объемы трафика, очень важно учитывать, как масштабируется код. Реактивное программирование хорошо справляется с этим - как в RxJava, так и в Reactor планировщики упрощают запуск кода между потоками оптимальным для масштабирования образом. Однако планировщики Reactor отличаются от RxJava. Это означает, что любая миграция с RxJava на Reactor требует некоторой адаптации. К счастью, планировщики не особо отличаются. Эта таблица суммирует большинство различий, необходимых для первоначального рефакторинга.

Вы также можете создавать новые экземпляры различных типов планировщиков с помощью методов newXXX. Например, Schedulers.newElastic (yourScheduleName) создает новый эластичный планировщик с именем yourScheduleName.

Реактор Null-Safety

В то время как RxJava 1.8 допускал все формы значений NULL, Reactor реализует Null-Safety, используя аннотации Spring. Это означает, что пустые значения требуют явной обработки, чтобы избежать потенциальных проблем с беспорядочным кодом, накладными расходами и исключениями NullPointerExceptions.

В пакете reactor.util.annotation представлены следующие аннотации:

  • @NonNull означает, что конкретный параметр, возвращаемое значение или поле не может быть null. (Не требуется для параметров и возвращаемого значения, когда применяется @NonNullApi).
  • @Nullable указывает, что параметр, возвращаемое значение или поле может быть null.
  • @NonNullApi - это аннотация на уровне пакета, которая указывает, что ненулевое значение является поведением по умолчанию для параметров и возвращаемых значений.

Источник: https://projectreactor.io/docs/core/release/reference/#null-safety

другие мысли

Лучшим результатом в Google для Проверенных исключений RxJava 1 является этот пост, в котором рекомендуется использовать блоки try-catch в реактивных потоках, чтобы избежать распространения ошибок через onError. Однако команда Spring Reactor рекомендует разрешать исключениям распространяться ниже по течению на исключение времени выполнения и отмечать любые альтернативы: информация должна быть помещена в документацию / javadoc вашего реактивного API (и мы рекомендуем использовать подклассы RuntimeException inst ) . У одного из основных разработчиков Reactor есть рекомендации по обработке исключений здесь. Полезно рассмотреть рекомендуемый вариант, чтобы гарантировать, что любые реактивные потоки в вашем коде ведут себя должным образом.

Благодарим Nicolas Bridoux и Tomek Polański за их статьи о миграции RxJava 1 - ›2, которые помогли повлиять на моменты, которые я здесь подчеркнул. Я лично не проходил через миграцию RxJava, поэтому большая часть информации, которую я включил, основывается на исследованиях, анекдотична и основана на случаях, которыми они поделились.