Реактивное программирование простыми словами — объясняют эксперты
Мы уже разобрались с такими парадигмами, как динамическое, декларативное и императивное программирование. Настал черёд реактивного.
В предыдущих статьях эксперты объяснили нам, что такое объектно-ориентированное, динамическое, декларативное и императивное программирование. В этот раз узнали у них, что из себя представляет реактивное программирование.
Что такое реактивное программирование?
Павел Романченко
технический директор центра инновационных технологий и решений «Инфосистемы Джет»
Классический подход к программированию предполагает выполнение запроса (к базе, сервисам и т.д.), ожидание ответа и продолжение работы. Во время ожидания поток простаивает. Масштабируют такие системы путём увеличения количества потоков, при этом вычисление оптимального количества потоков становится непростой задачей, так как сильно зависит от характера нагрузки, количества и качества ожиданий. И несмотря на это, избежать на 100% простоя ресурсов не удается.
В реактивном программировании обработка делится на большое количество небольших задач, выполнение каждой из которых оканчивается неким событием. На возникновение этого события реагирует соответствующий обработчик и выполняет свою задачу, опять генерирует событие и общий процесс обработки продолжается. Генерация события и реакция на него происходят асинхронно. Обработчик (их ещё называют акторами) выбирает следующее событие из очереди, обрабатывает и складывает событие в очередь другому обработчику. Если задача заключается в выполнении запроса в базе данных, то актор посылает запрос на сервер. После получения ответа будет сгенерировано событие с результатом запроса и запущен соответствующий актор.
Всё это позволяет более эффективно занять ресурсы полезной работой и управлять масштабированием. Повышается отзывчивость приложений. Кроме того, реактивное программирование помогает масштабироваться горизонтально.
Камиль Закиев
senior developer IT-компании Maxima
Понятие реактивного программирования тесно связано с понятием модели распространения данных, которая бывает двух типов:
- pull-модель.
- push-модель.
На самом деле, эти модели вполне логичны и естественны, поэтому их проявления можно проследить даже в обычной жизни.
Допустим, Вася любит быть в курсе всех новостей по хоккею и поэтому периодически посещает тематические сайты в надежде обнаружить интересный контент.
Его коллеге — Пете — тоже интересен хоккей, но в отличие от Васи, Петя подписался на рассылку новостей и получает уведомления о важных событиях в хоккее.
С точки зрения программирования, Вася использует pull-модель: периодически просматривает источники данных, в то время как Петя — push-модель: занимается обработкой входящих сообщений.
Таким образом, реактивное программирование — это стиль написания кода, который упрощает реализацию приложений, основанных на push-модели.
На практике этот стиль применяется для обработки входящего потока данных, например:
- сообщения от пользователей;
- уведомления об изменении расписания;
- действия пользователя с интерфейсом и т.д
Константин Коптяев
руководитель направления платформы Bercut
Реактивное программирование — это подход к разработке ПО, который строится на реагировании на события и на распространении событий. При этом модель реакции на события предполагает возможность простого распространения этих или трансформированных событий далее по системе. Ярким примером реализации реактивного подхода может служить таблица Excel. В ней существует цепочка вычислений, разделённая на несколько ячеек: при изменении значения одной из ячеек в цепочке значения в зависимых ячейках пересчитываются автоматически.
В целом, идея реактивного программирования призвана упростить создание сложных систем. В сложной большой системе возникает значительное количество разнообразных событий, каждое требует определённого механизма реакции и обработки. При использовании реактивного программирования события объединяются в потоки, а компоненты системы являются обработчиками потока событий (и также в свою очередь могут являться генераторами событий). Таким образом, сколь ни была бы сложна система и сколько бы в ней ни было разнообразных событий, вся система строится по принципу генерации потоков событий и реакции на них. Подход в моделировании сложной системы позволяет обрабатывать каждое событие асинхронно и изолированно от других. Важно понимать при этом, что дизайн реактивной системы предполагает, что любая функциональность в системе реализуется с помощью событий и обработчиков. Благодаря этому достигается уменьшение связанности между компонентами системы, увеличение гибкости, упрощение масштабирования и повышение устойчивости систем.
Дмитрий Немов
руководитель мобильной разработки SimbirSoft
Парадигма реактивного программирования включает отслеживание определенных событий и реагирование на них в асинхронных потоках данных. Эта идея предполагает, что существует источник событий и слушатель событий, который реагирует на события источника.
Суть реактивного программирования можно изобразить разными способами. Например, представим реку, течение которой несет несколько разноцветных объектов (мячей). Допустим, на берегу сидит человек, которому нужно выловить объекты с определенной характеристикой – только зеленые или красные. Если нам требуется запрограммировать подобную ситуацию, то реактивный подход – это то, что поможет нам оперировать потоками данных, получать данные, совершать математические вычисления и т.д. Работа с потоками требует от программиста определенного опыта и понимания, что и как комбинировать, какие операторы подходят для решения задачи.
Кейс: реактивный подход в высоконагруженном приложении на примере сервиса для начисления кэшбэка
Реактивный подход активно используется в Frontend-разработке и мобильной разработке, одним из его популяризаторов является Netflix.
Глеб Лысов
старший разработчик систем автоматизации и поддержки сервисов мониторинга и реагирования на киберугрозы в BI.ZONE
Само название парадигмы наводит на мысль, что реактивное программирование подразумевает какую-либо реакцию на изменения. Так и есть: реактивное программирование удобно при создании интерфейсов и построении моделей систем, изменяющихся во времени.
Реальным применением этой парадигмы может стать веб-фреймворк, поддерживающий архитектуру MVC (Model-View-Controller), в котором при изменении модели изменяется поведение пользовательских представлений.
Предположим, есть модель Пользователь с полями Имя и Фамилия. Добавляем поле Отчество — и во всех местах, будь это форма на сайте или структура базы данных, происходят соответствующие изменения: форма при следующей генерации содержит новое поле, а для БД генерируется миграция.
В рамках парадигмы чаще всего используют функции обратного вызова (callback) и конструкции асинхронного программирования (конкретные виды зависят от языка). Также здесь задействуют события (events) или потоки (flows). Если в коде есть такие «следы», значит, с большой вероятностью здесь применяется концепция реактивного программирования
Итак, что из себя представляет реактивное программирование?
В реактивном программировании обработка делится на большое количество небольших задач, выполнение каждой из которых оканчивается неким событием. На событие реагирует обработчик, который выполняет свою задачу и снова генерирует событие. Генерация события и реакция на него происходят асинхронно.Идея реактивного программирования призвана упростить создание и масштабирование сложных систем.Примером реактивного подхода может служить таблица Excel. В ней существует цепочка вычислений, разделённая на несколько ячеек: при изменении значения одной из ячеек значения в зависимых ячейках пересчитываются автоматически.
Напоминаем, что вы можете задать свой вопрос экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков рубрики. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на experts@tproger.ru, мы расскажем, как это сделать.
Реактивное программирование — Reactive programming
Реактивное программирование было впервые разработано Гленном Вадденом в 1986 году как язык программирования (VTScript) в системе диспетчерского управления и сбор данных (SCADA ).
В вычислениях, реактивное программирование — это декларативная парадигма программирования, связанная с потоками данных и распространение изменений. С помощью этой парадигмы можно с легкостью выразить статические (например, массивы) или динамические (например, источники событий) потоки данных, а также сообщить, что предполагаемая зависимость в связанной модели выполнения существует, что облегчает автоматическое распространение измененных данных. поток.
Например, в настройке императивного программирования a: = b + c будет означать, что a < \ displaystyle a>присваивается результат b + c в момент вычисления выражения, а затем значения b и c можно изменить без влияния на значение a . С другой стороны, при реактивном программировании значение a автоматически обновляется всякий раз, когда значения b или c изменение без необходимости повторного выполнения инструкции программе a: = b + c для определения присвоенного в настоящее время значения a.
Другим примером является язык описания оборудования, такой как Verilog, где реактивное программирование позволяет моделировать изменения по мере их распространения по схемам.
Реактивное программирование было предложено как способ упростить создание интерактивных пользовательских интерфейсов и системной анимации в режиме, близком к реальному времени.
Например, в модель – представление – контроллер ( MVC), реактивное программирование может облегчить изменения в базовой модели, которые автоматически отражаются в связанном представлении.
- 1 Подходы к созданию языков реактивного программирования
- 2 Модели и семантика программирования
- 3 Методы реализации и проблемы
- 3.1 Суть реализаций
- 3.1.1 Алгоритмы распространения изменений
- 3.1.2 Что подтолкнуть?
- 3.2.1 Сбои
- 3.2.2 Циклические зависимости
- 3.2.3 Взаимодействие с изменяемым состоянием
- 3.2.4 Динамическое обновление графика зависимостей
- 4.1 Степени ясности
- 4.2 Статическое или динамическое
- 4.3 Реактивное программирование высшего порядка
- 4.4 Дифференциация потоков данных
- 4.5 Модели оценки реактивного программирования
- 4.5.1 Сходства с шаблоном наблюдателя
- 5.1 Императив
- 5.2 Объектно-ориентированный
- 5.3 Функциональный
- 5.4 Основанный на правилах
Подходы к созданию реактивных языков программирования
При создании реактивных языков программирования используются несколько популярных подходов. Спецификация выделенных языков, специфичных для различных ограничений домена. Такие ограничения обычно характеризуются описанием встроенных вычислений или оборудования в реальном времени. Другой подход включает в себя спецификацию языков общего назначения, которые включают поддержку реактивности. Другие подходы сформулированы в определении и использовании программных библиотек или встроенных предметно-ориентированных языков, которые обеспечивают реактивность наряду с языком программирования или поверх него. Спецификация и использование этих различных подходов приводит к компромиссу языковых возможностей. В целом, чем более ограничен язык, тем больше связанные с ним компиляторы и инструменты анализа могут информировать разработчиков (например, при выполнении анализа того, могут ли программы выполняться в реальном времени). Функциональные компромиссы в специфичности могут привести к ухудшению общей применимости языка.
Модели и семантика программирования
Семейство реактивного программирования управляется множеством моделей и семантики. Мы можем условно разделить их по следующим параметрам:
- Синхронность: является ли лежащая в основе модель времени синхронным или асинхронным?
- Детерминизм: детерминированный или недетерминированный как в процессе оценки, так и в результатах
- Процесс обновления: обратные вызовы по сравнению с потоком данных по сравнению с субъектом
Методы и проблемы реализации
Суть реализаций
Среда выполнения реактивного языка программирования представлена графиком, который определяет зависимости между задействованные реактивные значения. В таком графе узлы представляют собой процесс вычисления, а отношения зависимости модели ребер. Такая среда выполнения использует упомянутый граф, чтобы отслеживать различные вычисления, которые должны быть выполнены заново, как только задействованный ввод изменяет значение.
Алгоритмы распространения изменений
Наиболее распространенные подходы к распространению данных:
- Извлечение : Потребитель значения фактически является проактивным, поскольку он регулярно запрашивает значения у наблюдаемого источника. и реагирует всякий раз, когда доступно соответствующее значение. Эта практика регулярной проверки событий или изменений значений обычно называется опросом.
- Push : Потребитель значения получает значение из источника всякий раз, когда значение становится доступным. Эти значения самодостаточны, например они содержат всю необходимую информацию, и у потребителя нет необходимости запрашивать дополнительную информацию.
- Push-pull : Потребитель значения получает уведомление об изменении, которое является кратким описанием изменения, например «какое-то значение изменено» — это нажимная часть. Однако уведомление не содержит всей необходимой информации (т.е. не содержит фактических значений), поэтому потребитель должен запросить у источника дополнительную информацию (конкретное значение) после того, как он получит уведомление — это извлекающая часть. Этот метод обычно используется, когда существует большой объем данных, которые могут быть потенциально интересны потребителям. Таким образом, чтобы уменьшить пропускную способность и задержку, отправляются только легкие уведомления; а затем те потребители, которым требуется дополнительная информация, будут запрашивать эту конкретную информацию. У этого подхода также есть недостаток, заключающийся в том, что источник может быть перегружен множеством запросов на дополнительную информацию после отправки уведомления.
Что отправлять?
На уровне реализации реакция на событие состоит из распространения информации на графике, которая характеризует наличие изменения. Следовательно, вычисления, на которые влияет такое изменение, затем становятся устаревшими и должны быть помечены для повторного выполнения. Такие вычисления обычно характеризуются переходным замыканием изменения в ассоциированном с ним источнике. Распространение изменений может затем привести к обновлению значения стоков графа.
Информация, распространяемая графиком, может состоять из полного состояния узла, то есть результата вычислений задействованного узла. В таких случаях предыдущий вывод узла игнорируется. Другой метод включает в себя распространение дельты, то есть постепенное распространение изменений. В этом случае информация распространяется по ребрам графа, которые состоят только из дельт, описывающих, как был изменен предыдущий узел. Этот подход особенно важен, когда узлы содержат большие объемы данных о состоянии, которые в противном случае было бы дорого пересчитывать с нуля.
Дельта-распространение — это, по сути, оптимизация, которая была тщательно изучена с помощью дисциплины инкрементных вычислений, подход которой требует удовлетворения во время выполнения, включая проблему обновления представления. Эта проблема, как известно, характеризуется использованием сущностей базы данных, которые отвечают за поддержку изменяющихся представлений данных.
Другой распространенной оптимизацией является использование унарного накопления изменений и пакетного распространения. Такое решение может быть более быстрым, поскольку оно сокращает обмен данными между задействованными узлами. Затем можно использовать стратегии оптимизации, которые определяют характер изменений, содержащихся внутри, и вносят соответствующие изменения. например два изменения в пакете могут отменять друг друга и, таким образом, просто игнорироваться. Еще один доступный подход описан как распространение уведомления о недействительности. Этот подход заставляет узлы с недопустимыми входными данными извлекать обновления, что приводит к обновлению их собственных выходных данных.
Существует два основных способа построения графа зависимостей:
- Граф зависимостей поддерживается неявно внутри цикла событий. Регистрация явных обратных вызовов приводит к созданию неявных зависимостей. Следовательно, инверсия управления, которая вызывается обратным вызовом, остается на месте. Однако выполнение функций обратного вызова (т.е. возврат значения состояния вместо значения единицы) требует, чтобы такие обратные вызовы стали композиционными.
- График зависимостей зависит от программы и создается программистом. Это облегчает адресацию инверсии управления обратным вызовом двумя способами: либо граф указывается явно (обычно с использованием предметно-ориентированного языка (DSL), который может быть встроен), либо граф неявно определяется с помощью выражение и генерация с использованием эффективного архетипического языка.
Проблемы реализации в реактивном программировании
Сбои
При распространении изменений можно выбрать порядок распространения таким образом, чтобы значение выражения было не естественное следствие исходной программы. Мы можем легко проиллюстрировать это на примере. Предположим, секунд — это реактивное значение, которое изменяется каждую секунду, чтобы представить текущее время (в секундах). Рассмотрим это выражение:
t = секунды + 1 g = (t>секунды)
Поскольку t всегда должно быть больше, чем секунд , это выражение всегда должно оценить истинное значение. К сожалению, это может зависеть от порядка оценки. При изменении секунд необходимо обновить два выражения: секунд + 1 и условное. Если первое выполняется раньше второго, то этот инвариант будет сохраняться. Если, однако, условие обновляется первым, используя старое значение t и новое значение секунд , тогда выражение будет оцениваться как ложное значение. Это называется глюк.
Некоторые реактивные языки не содержат сбоев и подтверждают это свойство. Обычно это достигается путем топологической сортировки выражений и обновления значений в топологическом порядке. Однако это может иметь последствия для производительности, например задержку доставки значений (из-за порядка распространения). Поэтому в некоторых случаях реактивные языки допускают сбои, и разработчики должны знать о возможности того, что значения могут временно не соответствовать исходному тексту программы и что некоторые выражения могут оцениваться несколько раз (например, t>секунды может оцениваться дважды: один раз при поступлении нового значения секунд и еще раз при обновлении t ).
Циклические зависимости
Топологическая сортировка зависимостей зависит от того, является ли граф зависимостей направленным ациклическим графом (DAG). На практике программа может определять граф зависимостей с циклами. Обычно языки реактивного программирования ожидают, что такие циклы будут «прерваны» путем размещения некоторого элемента вдоль «заднего края», чтобы разрешить завершение реактивного обновления. Как правило, языки предоставляют такой оператор, как delay , который используется механизмом обновления для этой цели, поскольку delay подразумевает, что последующее должно быть оценено на «следующем временном шаге» (позволяя текущую оценку прекратить).
Взаимодействие с изменяемым состоянием
Реактивные языки обычно предполагают, что их выражения чисто функциональны. Это позволяет механизму обновления выбирать разные порядки для выполнения обновлений и оставлять конкретный порядок неуказанным (тем самым обеспечивая оптимизацию). Однако, когда реактивный язык встроен в язык программирования с состоянием, программисты могут выполнять изменяемые операции. Как сделать это взаимодействие гладким, остается открытой проблемой.
В некоторых случаях возможны принципиальные частичные решения. Два таких решения включают:
- Язык может предлагать понятие «изменяемая ячейка». Изменяемая ячейка — это ячейка, о которой знает система реактивного обновления, поэтому изменения, внесенные в ячейку, распространяются на остальную часть реактивной программы. Это позволяет нереактивной части программы выполнять традиционную мутацию, в то же время позволяя реактивному коду узнавать об этом обновлении и реагировать на него, таким образом поддерживая согласованность отношений между значениями в программе. Примером реактивного языка, который предоставляет такую ячейку, является FrTime.
- Правильно инкапсулированные объектно-ориентированные библиотеки предлагают инкапсулированное понятие состояния. В принципе, таким образом, такая библиотека может беспрепятственно взаимодействовать с реактивной частью языка. Например, обратные вызовы могут быть установлены в геттерах объектно-ориентированной библиотеки для уведомления механизма реактивного обновления об изменениях состояния, а изменения в реактивном компоненте могут быть переданы объектно-ориентированной библиотеке через геттеры. Такую стратегию использует FrTime.
Динамическое обновление графа зависимостей
В некоторых реактивных языках граф зависимостей статичен, то есть граф фиксируется на протяжении всего выполнения программы. В других языках граф может быть динамическим, то есть он может изменяться по мере выполнения программы. В качестве простого примера рассмотрим этот иллюстративный пример (где секунд — реактивное значение):
t = if ((seconds mod 2) == 0): секунды + 1 else: секунды - 1 end t + 1
Каждую секунду значение этого выражения изменяется на другое реактивное выражение, от которого затем зависит t + 1 . Поэтому график зависимостей обновляется каждую секунду.
Разрешение динамического обновления зависимостей обеспечивает значительную выразительную мощность (например, динамические зависимости обычно возникают в программах графического интерфейса пользователя (GUI)). Однако механизм реактивного обновления должен решить, восстанавливать ли выражения каждый раз или оставить узел выражения созданным, но неактивным; в последнем случае убедитесь, что они не участвуют в вычислениях, когда они не должны быть активными.
Концепции
Степени явности
Реактивные языки программирования могут варьироваться от очень явных, где потоки данных настраиваются с помощью стрелок, до неявных, где потоки данных происходят из языковые конструкции, похожие на конструкции императивного или функционального программирования. Например, в неявно поднятом функциональном реактивном программировании (FRP) вызов функции может неявно вызывать создание узла в графе потока данных. Библиотеки реактивного программирования для динамических языков (например, библиотеки Lisp «Cells» и Python «Trellis») могут создавать граф зависимостей на основе анализа значений, считываемых во время выполнения функции, во время выполнения, что позволяет спецификациям потока данных быть как неявными, так и динамическими.
Иногда термин реактивное программирование относится к архитектурному уровню разработки программного обеспечения, где отдельные узлы в графе потока данных представляют собой обычные программы, которые взаимодействуют друг с другом.
Статическое или динамическое
Реактивное программирование может быть чисто статическим, если потоки данных настроены статически, или динамическим, когда потоки данных могут изменяться во время выполнения программы.
Использование переключателей данных в графе потока данных может в некоторой степени сделать статический граф потока данных динамическим и слегка размыть различия. Однако истинное динамическое реактивное программирование может использовать императивное программирование для восстановления графа потока данных.
Реактивное программирование высшего порядка
Можно сказать, что реактивное программирование относится к более высокому порядку, если оно поддерживает идею о том, что потоки данных могут использоваться для построения других потоков данных. То есть результирующее значение из потока данных представляет собой другой граф потока данных, который выполняется с использованием той же модели оценки, что и первый.
Дифференциация потока данных
В идеале все изменения данных распространяются мгновенно, но на практике этого нельзя гарантировать. Вместо этого может потребоваться дать разным частям графа потока данных разные приоритеты оценки. Это можно назвать дифференцированным реактивным программированием .
. Например, в текстовом процессоре маркировка орфографических ошибок не обязательно должна полностью синхронизироваться с вставкой символов. Здесь потенциально можно использовать дифференцированное реактивное программирование, чтобы придать программе проверки орфографии более низкий приоритет, что позволяет отложить ее выполнение, сохраняя при этом другие потоки данных мгновенными.
Однако такое различие вносит дополнительную сложность в конструкцию. Например, решение о том, как определять различные области потока данных и как обрабатывать передачу событий между различными областями потока данных.
Модели оценки реактивного программирования
Оценка реактивных программ не обязательно основана на том, как оцениваются языки программирования на основе стека. Вместо этого, когда некоторые данные изменены, изменение распространяется на все данные, которые частично или полностью получены из данных, которые были изменены. Это распространение изменений может быть достигнуто несколькими способами, из которых, возможно, наиболее естественным способом является схема недействительности / ленивого повторного подтверждения.
Может быть проблематично просто наивно распространять изменение с использованием стека из-за потенциальной экспоненциальной сложности обновления, если структура данных имеет определенную форму. Одна такая форма может быть описана как «повторяющаяся форма ромбов» и имеет следующую структуру: A n→Bn→An + 1, A n→Cn→An + 1, где n = 1,2. Эту проблему можно преодолеть, распространяя аннулирование только тогда, когда некоторые данные еще не признаны недействительными, и позже повторно проверяйте данные, когда это необходимо, используя ленивую оценку.
. Одна неотъемлемая проблема для реактивного программирования заключается в том, что большинство вычислений, которые будут оцениваться и забытые в обычном языке программирования, должны быть представлены в памяти как структуры данных. Это потенциально может сделать реактивное программирование очень затратным по памяти. Однако исследование того, что называется понижением, потенциально могло бы преодолеть эту проблему.
С другой стороны, реактивное программирование — это форма того, что можно было бы описать как «явный параллелизм», и поэтому оно может быть полезным для использования мощности параллельного оборудования.
Сходства с шаблоном наблюдателя
Реактивное программирование имеет принципиальное сходство с шаблоном наблюдателя , обычно используемым в объектно-ориентированном программировании. Однако интеграция концепций потока данных в язык программирования упростит их выражение и, следовательно, может увеличить степень детализации графа потока данных. Например, шаблон наблюдателя обычно описывает потоки данных между целыми объектами / классами, тогда как объектно-ориентированное реактивное программирование может быть нацелено на члены объектов / классов.
Подходы
Императив
Можно объединить реактивное программирование с обычным императивным программированием. В такой парадигме императивные программы работают с реактивными структурами данных. Такая установка аналогична; однако, в то время как императивное программирование с ограничениями управляет двунаправленными ограничениями, реактивное императивное программирование управляет односторонними ограничениями потока данных.
Объектно-ориентированное
Объектно-ориентированное реактивное программирование (OORP) — это комбинация объектно-ориентированного программирования и реактивного программирования. Возможно, наиболее естественный способ создать такую комбинацию заключается в следующем: вместо методов и полей у объектов есть реакции, которые автоматически переоцениваются, когда другие реакции, от которых они зависят, были изменены.
Если язык OORP поддерживает его императивные методы, он также подпадал бы под категорию императивного реактивного программирования.
Функциональный
На основе правил
Относительно новая категория языков программирования использует ограничения (правила) в качестве основной концепции программирования. Он состоит из реакций на события, которые удовлетворяют все ограничения. Это не только облегчает реакции, основанные на событиях, но и делает реактивные программы инструментом правильности программного обеспечения. Примером языка реактивного программирования на основе правил является Ampersand, который основан на алгебре отношений.
См. Также
- Реактивные расширения, API для реализации реактивного программирования с потоками, наблюдаемыми объектами и операторами с несколькими языками. реализации, включая RxJs, RxJava, RxPy и RxSwift.
- Elm (язык программирования) Реактивная композиция веб-интерфейса пользователя.
- Реактивные потоки, стандарт JVM для обработки асинхронных потоков с неблокирующим противодавлением
- Наблюдаемое, наблюдаемое в реактивном программировании.
Ссылки
- ^«О трехграннике». VTScada от Трехгранного. Проверено 2020-10-20.
- ^«SCADA Scripting Language». VTScada от Трехгранного. Проверено 20 октября 2020 г.
- ^Trellis, Модель-представление-контроллер и шаблон наблюдателя, Tele community.
- ^«Встраивание динамического потока данных в язык вызова по значению». cs.brown.edu. Проверено 9 октября 2016 г.
- ^«Пересечение границ состояний: адаптация объектно-ориентированных структур к функциональным реактивным языкам». cs.brown.edu. Проверено 9 октября 2016 г.
- ^«Реактивное программирование — Искусство обслуживания | Руководство по управлению ИТ». theartofservice.com. Проверено 2 июля 2016.
- ^Берчетт, Кимберли; Купер, Грегори Х; Кришнамурти, Шрирам, «Понижение: метод статической оптимизации для прозрачной функциональной реактивности», Труды симпозиума ACM SIGPLAN 2007 года по частичной оценке и манипулированию программами на основе семантики (PDF), стр. 71–80.
- ^Деметреску, Камил; Финокки, Ирэн; Рибичини, Андреа, «Реактивное императивное программирование с ограничениями потока данных», Труды международной конференции ACM 2011 года по системным языкам и приложениям объектно-ориентированного программирования, стр. 407–26.
- ^Йостен, Стеф (2018), «Алгебра отношений как язык программирования с использованием компилятора Ampersand», Журнал логических и алгебраических методов программирования, 100, стр. 113–29, doi : 10.1016 / j. jlamp.2018.04.002.
Внешние ссылки
- Обзор реактивного программирования Статья Э. Байномугиши, А. Ломбида Карретона, Т. Ван Катсема, С. Мостинкса и В. Де Мойтера, в которой исследуются и обеспечивает систематизацию существующих подходов к реактивному программированию.
- Проект MIMOSA INRIA — ENSMP, общий сайт о реактивном программировании.
- Эксперименты с ячейками Демонстрация простого приложения реактивного программирования на Лиспе с использованием Библиотека ячеек
- REScala Реактивное программирование для объектно-ориентированных приложений.
- Устарел шаблон наблюдателя A 2010 статья Инго Майера, Тиарка Ромпфа и Мартина Одерски, описывающая структуру реактивного программирования для языка программирования Scala.
- Устарение шаблона наблюдателя с помощью Scala.React Статья Инго * RxJS, библиотека Reactive Extensions для «составления асинхронных [. ] программ с использованием наблюдаемых последовательностей»
ПРИМЕНЕНИЕ РЕАКТИВНОГО ПРОГРАММИРОВАНИЯ И МОДЕЛИ КОМПЛЕКСНОЙ ОБРАБОТКИ СОБЫТИЙ Текст научной статьи по специальности «Компьютерные и информационные науки»
Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Алпатов Алексей Николаевич, Сороков Артём Сергеевич
Реактивное программирование становится все более актуальным в современном программировании. Это связано с тем, что сейчас все больше приложений работают в режиме реального времени и требуют быстрого и эффективного обработки данных. Данный подход предоставляет инструменты для создания таких приложений, которые могут быстро реагировать на изменения внешних условий и обрабатывать большие объемы данных. Статья рассматривает принципы и концепции реактивного программирования и модели комплексной обработки событий, а также примеры их применения. Мы также обсудим преимущества использования этих подходов вместе.
i Надоели баннеры? Вы всегда можете отключить рекламу.
Похожие темы научных работ по компьютерным и информационным наукам , автор научной работы — Алпатов Алексей Николаевич, Сороков Артём Сергеевич
РЕАЛИЗАЦИЯ ВЕБ СЕРВИСА С ПРИМЕНЕНИЕМ ПАРАДИГМЫ РЕАКТИВНОГО ПРОГРАММИРОВАНИЯ
Система выполнения моделей машинного обучения на потоке событий
Обзор состояния области потоковой обработки данных
Использование реактивного программирования при разработке мобильных приложенийИспользование технологии программирования Command Query Responsibility Segregation (cqrs) для распределенных информационных сервисов
i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.APPLICATION OF REACTIVE PROGRAMMING AND COMPLEX EVENT PROCESSING MODELS
Reactive programming is becoming more and more relevant in modern programming. This is due to the fact that now more and more applications work in real time and require fast and efficient data processing. This approach provides the tools to create such applications that can quickly respond to changes in external conditions and process large amounts of data. The article considers the principles and concepts of reactive programming and complex event processing models, as well as examples of their application. We will also discuss the benefits of using these approaches together.
Текст научной работы на тему «ПРИМЕНЕНИЕ РЕАКТИВНОГО ПРОГРАММИРОВАНИЯ И МОДЕЛИ КОМПЛЕКСНОЙ ОБРАБОТКИ СОБЫТИЙ»
Столыпинский вестник №5/2023
Научная статья Original article УДК 004.4
DOI 10.55186/27131424 2023 5 5 7
ПРИМЕНЕНИЕ РЕАКТИВНОГО ПРОГРАММИРОВАНИЯ И МОДЕЛИ КОМПЛЕКСНОЙ ОБРАБОТКИ СОБЫТИЙ
APPLICATION OF REACTIVE PROGRAMMING AND COMPLEX EVENT PROCESSING MODELS
Алпатов Алексей Николаевич, кандидат технических наук, доцент кафедры «Инструментального и прикладного программного обеспечения», РТУ МИРЭА, Россия, г. Москва
Сороков Артём Сергеевич, студент, 4 курс, институт информационных технологий, РТУ МИРЭА, Россия, г. Москва
Alpatov Alexey Nikolaevich, Candidate of Technical Sciences, Associate Professor of the Department of «Instrumental and Applied Software», RTU MIREA, Russia, Moscow, e-mail: alpatov@mirea.ru
Sorokov Artem Sergeevich, student 4th year, Institute of Information Technology, RTU MIREA, Russia, Moscow, e-mail: sorokov_a02@mail.ru
Реактивное программирование становится все более актуальным в современном программировании. Это связано с тем, что сейчас все больше
приложений работают в режиме реального времени и требуют быстрого и эффективного обработки данных. Данный подход предоставляет инструменты для создания таких приложений, которые могут быстро реагировать на изменения внешних условий и обрабатывать большие объемы данных.
Статья рассматривает принципы и концепции реактивного программирования и модели комплексной обработки событий, а также примеры их применения. Мы также обсудим преимущества использования этих подходов вместе.
Reactive programming is becoming more and more relevant in modern programming. This is due to the fact that now more and more applications work in real time and require fast and efficient data processing. This approach provides the tools to create such applications that can quickly respond to changes in external conditions and process large amounts of data.
The article considers the principles and concepts of reactive programming and complex event processing models, as well as examples of their application. We will also discuss the benefits of using these approaches together.
Ключевые слова: Реактивное программирование, комплексная обработка событий, Java.
Keywords: Reactive programming, complex event processing, Java.
В современном мире программное обеспечение стало неотъемлемой частью нашей жизни. Однако, с увеличением сложности приложений и возрастанием числа пользователей, стандартные подходы к программированию становятся недостаточно эффективными. В такой ситуации реактивное программирование и модель комплексной обработки событий могут стать решением проблемы. Также такой подход используется для разработки приложений, которые реагируют на изменения внешних
условий и событий, а не просто выполняют последовательность заданных действий.
Реактивное программирование — это подход к программированию, который использует асинхронные потоки данных и событий для создания более отзывчивых приложений. Основные принципы реактивного программирования включают в себя: реактивность, асинхронность, отзывчивость, управление ошибками и распределенность.
Реактивные приложения обычно состоят из компонентов, которые могут быть связаны друг с другом через потоки данных. Эти потоки данных могут быть как однонаправленными, так и двунаправленными. Потоки данных могут также быть преобразованы, комбинированы и агрегированы с помощью функциональных операций, таких как отображение, фильтрация, свертка и т.д.
Существует множество инструментов и библиотек, которые поддерживают реактивное программирование. Некоторые из них включают в себя ReactiveX, Akka, RxJava, Project Reactor и другие. Эти инструменты предоставляют различные способы создания и управления потоками данных, а также реализацию других принципов реактивного программирования.
Примером применения реактивного программирования может быть система мониторинга, которая собирает и анализирует данные из разных источников в реальном времени. В такой системе, данные могут поступать из различных источников, таких как датчики, логи и базы данных. Реактивное программирование может быть использовано для эффективной обработки потоков данных, фильтрации, трансформации и агрегации данных, а также для мгновенной обработки ошибок и исключений.
Модель комплексной обработки событий (Complex Event Processing, CEP) — это методология обработки и анализа потоков событий с целью выявления сложных паттернов и событий, которые не могут быть обнаружены с помощью простых правил и условий. CEP используется в различных
областях, таких как финансы, здравоохранение, телекоммуникации, транспорт и другие.
Основная идея CEP заключается в том, чтобы обработать и анализировать потоки событий в реальном времени с использованием сложных алгоритмов и правил, которые позволяют выявлять скрытые связи и зависимости между событиями. Например, CEP может быть использован для мониторинга финансовых рынков и выявления аномалий или для определения паттернов в здоровье пациентов на основе их медицинских данных.
Существует множество инструментов и библиотек, которые поддерживают модель комплексной обработки событий. Некоторые из них включают в себя Apache Flink, Esper, StreamBase и другие.
Реактивное программирование и модель комплексной обработки событий могут быть использованы вместе для создания масштабируемых, отказоустойчивых и высокопроизводительных систем обработки данных в реальном времени. Эти подходы могут быть комбинированы для обработки сложных потоков данных, выявления скрытых паттернов и аномалий, а также для эффективной обработки ошибок и исключений.
Например, система мониторинга финансовых рынков может использовать реактивное программирование для управления и обработки потоков данных, а модель комплексной обработки событий может быть использована для анализа и принятия решений на основе этих данных. Комбинируя эти подходы, можно создать систему, которая быстро и эффективно анализирует большие объемы данных в реальном времени, выявляет изменения и аномалии на рынке и предлагает соответствующие действия.
Другим примером может быть система управления транспортным потоком, где реактивное программирование используется для управления потоками данных о движении транспорта, а модель комплексной обработки событий используется для принятия решений на основе этих данных,
например, для определения оптимального маршрута для транспорта в реальном времени.
Реактивное программирование и модель комплексной обработки событий являются эффективными подходами для создания масштабируемых, отказоустойчивых и высокопроизводительных систем обработки данных в реальном времени. Комбинируя эти подходы, можно создать систему, которая быстро и эффективно обрабатывает большие объемы данных, выявляет аномалии и принимает соответствующие действия. В будущем, эти подходы будут продолжать развиваться и улучшаться, что позволит создавать еще более мощные системы обработки данных.
Однако, необходимо учитывать, что реактивное программирование и модель комплексной обработки событий не всегда являются оптимальными решениями для всех задач обработки данных. Некоторые задачи могут быть решены более эффективно с использованием других подходов и инструментов.
Тем не менее, с ростом количества данных, которые необходимо обрабатывать в реальном времени, реактивное программирование и модель комплексной обработки событий становятся все более важными и необходимыми для создания эффективных систем обработки данных. Их использование позволяет создавать системы, которые обеспечивают высокую производительность, масштабируемость и отказоустойчивость, что является ключевым требованием для многих современных приложений и систем.
Таким образом, реактивное программирование и модель комплексной обработки событий представляют собой мощные инструменты для обработки данных в реальном времени и будут продолжать использоваться в будущем для создания высокопроизводительных и эффективных систем обработки данных.
Использование этих подходов может помочь разработчикам создавать системы, которые могут быстро и эффективно обрабатывать большие объемы
данных, выявлять аномалии и принимать соответствующие действия. Это особенно важно в условиях, когда время реакции на события является критически важным, например, в системах финансового мониторинга, телекоммуникационных сетях, системах безопасности и других подобных системах.
Будущее этих подходов обещает быть светлым, так как растущее количество данных и требования к обработке данных в реальном времени будут продолжать стимулировать развитие и усовершенствование этих подходов. Ожидается, что в будущем, эти подходы будут еще более широко применяться во многих отраслях, включая финансы, здравоохранение, транспорт и другие, что приведет к созданию новых возможностей для развития и инноваций.
Реактивное программирование и модель комплексной обработки событий представляют собой мощные инструменты для обработки данных в реальном времени. Результаты их использования уже показали свою эффективность в различных отраслях, включая финансы, здравоохранение, транспорт и другие.
Одним из главных достоинств этих подходов является их способность обрабатывать большие объемы данных и выявлять скрытые паттерны и аномалии в потоках данных. Это позволяет создавать системы, которые быстро и эффективно обрабатывают данные и принимают соответствующие действия.
Однако, применение реактивного программирования и модели комплексной обработки событий требует от разработчиков некоторой экспертизы и умения мыслить реактивно. Некоторые инструменты и библиотеки могут также быть достаточно сложными для использования, что может привести к трудностям при их внедрении.
Тем не менее, будущее этих подходов обещает быть светлым, так как растущее количество данных и требования к обработке данных в реальном
времени будут продолжать стимулировать развитие и усовершенствование этих подходов. Ожидается, что в будущем, эти подходы будут еще более широко применяться во многих отраслях, что приведет к созданию новых возможностей для развития и инноваций.
Таким образом, реактивное программирование и модель комплексной обработки событий являются перспективными подходами для обработки данных в реальном времени, и их применение будет продолжать расти и развиваться в будущем
1. Введение в реактивное программирование. [Электронный ресурс]. URL: https://habr.com/ru/companies/arcadia/articles/432004/ (дата обращения: 07.04.2023).
2. Реактивное программирование на Java: как, зачем и стоит ли? Часть I [Электронный ресурс]. https://habr.com/ru/companies/oleg-bunin/articles/543386/ (дата обращения: 10.04.2023).
3. Реактивное программирование на Java: как, зачем и стоит ли? Часть II [Электронный ресурс]. https://habr.com/ru/companies/oleg-bunin/articles/545702/ ls.html (дата обращения: 17.04.2023).
4. Обработка сложных событий. [Электронный ресурс]. URL: https://ru.wikibrief.org/wiki/Complex_event_processing (дата обращения: 20.04.2023).
5. Поиск событийных цепочек в реальном времени с CEP-библиотекой Apache Flink. [Электронный ресурс]. URL: https://bigdataschool.ru/blog/cep-library-flink-for-event-streaming-analytics.html (дата обращения: 07.05.2023).
6. Комплексная обработка событий — взгляд начинающего. [Электронный ресурс]. URL: https://coderlessons.com/articles/programmirovanie/kompleksnaia-obrabotka-sobytii-vzgliad-nachinaiushchego (дата обращения: 10.05.2023).
1. Introduction to reactive programming. [Electronic resource]. URL: https://habr.com/ru/companies/arcadia/articles/432004/ (date of access: 07.04.2023)
2. Reactive programming in Java: how, why and is it worth it? Part I [Electronic resource]. https://habr.com/ru/companies/oleg-bunin/articles/543386/ (date of access: 10.04.2023).
3. Reactive programming in Java: how, why and is it worth it? Part II [Electronic resource]. https://habr.com/ru/companies/oleg-bunin/articles/545702/ls.html (accessed 17.04.2023).
4. Handling complex events. [Electronic resource]. URL: https://ru.wikibrief.org/wiki/Complex_event_processing (accessed 20.04.2023).
5. Search for real-time event chains with the Apache Flink CEP library. [Electronic resource]. URL: https://bigdataschool.ru/blog/cep-library-flink-for-event-streaming-analytics.html (date of access: 07.05.2023).
6. Complex event processing — a beginner’s view. [Electronic resource]. URL: https://coderlessons.com/articles/programmirovanie/kompleksnaia-obrabotka-sobytii-vzgliad-nachinaiushchego (accessed 10.05.2023).
© Сороков А.С., 2023 Научный сетевой журнал «Столыпинский вестник» №5/2023
Для цитирования: Сороков А.С. ПРИМЕНЕНИЕ РЕАКТИВНОГО ПРОГРАММИРОВАНИЯ И МОДЕЛИ КОМПЛЕКСНОЙ ОБРАБОТКИ СОБЫТИЙ// Научный сетевой журнал «Столыпинский вестник» №5/2023
Архитектура и реактивное программирование
Что такое реактивное программирование? Не Rx. И даже не Excel. Это архитектурный паттерн, позволяющий абсолютно иначе писать код. В статье мы устаканим фундаментальные знания, утвердимся в том, что React.js всё же является реактивным, и подумаем о том, как и когда нужно, а когда не нужно применять паттерны реактивного программирования.
Так уж вышло, что я побывал в большом количестве огромных кодовых баз, где сталкивался с одними и теми же проблемами организации кода. Информация ниже — это результат исследований программирования в общем и реактивного программирования в частности за последние пять лет. Я уже несколько лет пишу свой менеджер состояния Reatom, и это не просто пет-проект, а серьёзный продукт. Я старался сделать его проще для входа и использования, но оставил возможность расти до энтерпрайза и решать соответствующие проблемы. В статье будет не теория из пустых рассуждений, а опыт решения реальных задач.
Вы уже могли видеть связанную статью «Что такое состояние», где я подробно разбираю этот вопрос. А сегодня поговорим о второй стороне управления состоянием и потоками данных.
Проблемы, которые реактивное программирование поможет вам решить:
- связанность кода и его разбиение на модули;
- ленивая подгрузка модулей;
- автоматическая инвалидация кеша.
▍ Определение
Реактивное программирование — парадигма программирования, предполагающая передачу ответственности за инициализацию обработки информации источнику информации.
Это определение — описание того общего, что есть у Rx и MobX — таких разных, но безусловно реактивных библиотек, признанных всей индустрией. Да, они полностью отличаются внешним API, но подкапотные механизмы одни и те же — динамический список подписчиков и какое-то условие (триггер) его обхода. Это очень простая механика, и она встречается постоянно. В этом нет ничего сакрального, многие стандартные API платформы и библиотеки её реализуют. Иногда доступ к подписчикам явный — метод subscribe или effect . Иногда он скрыт — JSX тег разворачивается в React.createElement , который ведёт к подписке на внутренний стейт.
Да, React.js также использует паттерны реактивного программирования, это легко проверить. Есть ли у нас контроль над выполнением функции рендера? Нет, React определяет это. Вы могли бы попросить запланировать обновление, но оно будет запущено, когда React примет решение об этом. Он несёт ответственность за запуск вычислений.
В документации React как-то мелькало утверждение о том, что React не ФРПшный и это так. Он не использует специфичные конструкции функционально-реактивного программирования, но немного использует реактивное программирование в общем.
Паттерны реактивного программирования предполагают разворачивание направления связей в нашем коде, позволяя одним модулям зависеть от других, не меняя их код. Иначе говоря, реактивное программирование позволяет уменьшить связанность кода.
Хорошей практикой при разработке приложений является разделение их на независимые виджеты / модули / компоненты / фичи — все называют их по-разному. Но важно понимать, что эти модули не существуют сами по себе, они имеют какой-то общий смысл, и цельное приложение получается из их связанности. Сложный вопрос — как описывать эти связи и где их хранить.
▍ Пример
Здесь и ниже мы будем находиться в контексте JavaScript, но все идеи относятся к любому ЯП.
У нас есть аватарка пользователя в шапке и на странице профиля, при её обновлении с этой страницы нужно подставить новый урл в двух местах. Классический подход: в handlePictureUpdate мы пишем такой код: document.querySelector(‘.profile .ava’).src = newSrc; document.querySelector(‘.header .ava’).src = newSrc . Важно тут то, что функция handlePictureUpdate находится в коде модуля профайла, но почему-то ходит в модуль шапки — это и есть связанность. Такой кодстайл имеет свойство расти по своей сложности, его чтение может давать не те результаты, на которые рассчитываешь, — код шапки не содержит информации о связи с профайлом. Всё это ведёт к неочевидным багам — мы обновили шапку, поменяв её класс на .app-header , и querySelector в handlePictureUpdate теперь будет падать с ошибкой TypeError: Cannot set properties of undefined (setting ‘src’) . Причём ошибку после такого изменения скорее всего не выявили бы, потому что она в другом модуле. А в интеграционных тестах профайла не было бы проверки того, что происходит в шапке — классика.
Кто-то скажет, что дело в отсутствии БЭМа и предсказуемых селекторов. Кто-то возмутится неиспользованием общей константы с названием селектора шапки. Кто-то укажет на отсутствие проверки на undefined после querySelector — TypeScript бы подсказал! Такой маленький пример и уже так много проблем. Но это всё вопросы прикладного кода, которые в разных ситуациях будут разными. Возможно ли решить проблему подобного характера с архитектурной точки зрения, принципиально избавившись от необходимости перепроверять один модуль при рефакторинге другого?
▍ DDD и SSoT
Принцип Single Source of Truth (SSoT) означает, что в системе существует единственный актуальный и согласованный источник данных определённого типа, а все связанные модели работают с данными и процессами этого типа только через этот источник. Предметно-ориентированное проектирование (domain-driven design, DDD) говорит нам о том, что эти типы данных должны исходить от бизнес-сущностей — доменов: профиль пользователя, сформированный заказ товара. Модель — реализация домена в коде, страница пользователя и форма просмотра и создания, редактирования заказа.
Оперируя бизнес-доменами и не привязываясь к интерфейсу пользователя, нам становится намного проще определять, что к чему относится. Если отвязать профиль от страницы, а заказ от формы — выделить отдельную модель под домен — то станет намного проще принимать решение о том, где хранить и обрабатывать путь к аватарке пользователя.
Пример-предыстория о всё том же адресе аватарки. Когда приложение только начинали делать, выделенной страницы пользователя не было, были только основные формы для реализации бизнеса: список товаров, оформление заказа. Со временем решили повысить UX пользователя, сделать интерфейс дружественным и домашним и решили отображать на нём всегда что-то очень знакомое для пользователя — его аватарку. При клике по квадратной заглушке в правой части шапки можно было выбрать картинку и загрузить её. Логика этой загрузки лежала, соответственно, в шапке. Ещё позже решили сделать страницу редактирования профиля, чтобы адрес можно было заранее сохранить и редактировать, ну и картинку покрупнее посмотреть, заменить и покропить. У программиста встала дилемма — двигать код загрузки из файла с шапкой в файл профайла или из профайла импортировать код из шапки, что странно. В итоге код был передвинут, но это там так и осталось: document.querySelector(‘.header .ava’).src = newSrc .
Теперь у нас в коде страницы профайла есть какое-то знание о коде шапки — не хорошо. В начале статьи разобрали, где это может сломаться. Но даже если мы попытаемся применить DDD и выделим код профайла отдельно от страницы профайла, завязка на интерфейс у нас всё равно останется: document.querySelector(‘.profile .ava’).src = newSrc; document.querySelector(‘.header .ava’).src = newSrc , просто будет лежать в другой папочке. Таким образом, отделяя домен бизнесовый, мы раздробили домен системный — код страницы профайла и код шапки. Шило на мыло.
▍ Проектирование с реактивным программированием
Реактивное программирование прекрасно решает подобные проблемы за счёт простого трюка — связи между модулей переносятся из кода в рантайм. В коде мы лишь описываем, что хотели бы получить, но не описываем как. Это очень важно. Когда мы говорим о высокой связанности как плохом запахе кода, мы имеем в виду переплетение в самом коде, в тексте файлов проекта. Написание кода, чтение и дебаг происходят в подавляющем большинстве при работе с текстом программы, с кодом в самом материальном смысле этого слова. Реактивные паттерны позволяют убрать код, но оставить логическую связь, образовав её в рантайме. Как мы знаем, лучший код тот, что не написан.
Вспоминая пример с обновлением аватарки в шапке, код выглядел бы так для шапки: profile.$picture.subscribe(src => < this.ava.src = src >) , в то время как в модели профайла нужно было бы просто экспортировать синглтон profile с picture , обёрнутым в контейнер Observable — $picture . Теперь код профиля ничего не знает о шапке, но она связана с ним наглядно и типобезопасно. Мы легко можем отследить эти связи при необходимости — Find all references в IDE, но сам код профайла остался максимально чистым и ёмким. По сути ничего не меняется, но код становится обслуживать легче. Повторю КДПВ.
▍ Ленивость
Тема небольшая и понятная, но проговорить её стоит. Проектируя модели с реактивными публичными интерфейсами, мы достигаем такого сильного уровня изоляции, что другие не завис имые , а зави сящие модели могут легко подключаться и отключаться в любой момент времени, позволяя включать и настраивать dynamic imports максимально легко.
▍ Инвалидация кеша
Ещё одна тема, по которой я пройдусь лишь вскользь, но не потому, что она простая, а потому, что слишком большая и требует отдельной статьи или серии статей. Задача этой статьи — сделать архитектурный обзор, на этом и сфокусируемся.
Мы знаем, что связи в нашей системе никуда не делись, но перенеслись из кода в рантайм, и рантаймом этим управляет какой-то утилитарный код. Обычно это интерфейс, который скрывает за собой список подписчиков и логику их обхода при поступлении новой информации, но как ещё это можно использовать? Я долго копаюсь в этой теме и с уверенностью могу сказать, что сложная реактивная система походит на упрощённую реализацию виртуальной машины. Тут и автоматическая очистка мусора (garbage collection), и инлайн кеши (мемоизация), и виртуальная адресация (скоупы / контексты), и ещё небольшая пачка разнообразных фич для метапрограммирования(?). Одна из таких фич лежит на поверхности — зная все места хранения данных и связи между ними, можно легко отслеживать инвалидацию данных и их связей. Главная прелесть такой оптимизации заключается в её автоматическом применении, что делает вопрос производительности всего приложения более предсказуемым.
Тема эта очень большая, на Хабре уже были хорошие статьи. Я лишь могу привести свою последнюю версию обзора возможных алгоритмов инвалидации и реакций. Вникать в это не обязательно.
▍ Что такое хорошо, что такое плохо
Интереснее всего поговорить о том, какую сложность и какие проблемы привносит реактивное программирование. Две первых лежат на поверхности: производительность и дебаг.
Мы привносим больше работы в рантайм — конструируя ещё один уровень абстракции, мы забираем память на его обслуживание (в большей степени) и в некоторых случаях вычислительную производительность (в меньшей степени). К счастью, используя современные техники автоматического кеширования (и автоматической инвалидации), мы можем отыграть потраченные ресурсы на том, что уменьшим количество вычислений в доменном коде.
Большей проблемой является дополнительная ментальная нагрузка и сложность дебага дополнительных структур в рантайме. Данные уже нельзя просто увидеть в переменной, гуляя по коду с отладчиком — нужно запросить их из реактивного контейнера, написав дополнительный код в консоли, в лучшем случае раскрыть свойство-геттер, что может иметь неприятные сайд-эффекты. Дополнительные девтулзы могут помочь в этом вопросе, но всё ещё нет библиотек, где они были бы развиты достаточно хорошо, чтобы можно было в большинстве случаев отказаться от нативного дебагера.
Самой большой проблемой, на моей практике, является непонимание, когда эту реактивность применять, а когда нет. Дело в том, что внедряя реактивные интерфейсы в наш код, мы его красим. Как асинхронные функции заставляют везде писать await, чтобы получить из них данные, так и реактивные примитивы удобно использовать тогда, когда весь код на них опирается. Соответственно, когда все ключевые сущности имеют дополнительный реактивный интерфейс, программисту кажется что именно его и нужно использовать для всех задач. Но это не так.
В своём канале я опубликовал сравнение реализации очень простой задачи на двух разных реактивных библиотеках. Одна пропагандирует описывать вообще все связи реактивно, вторая подразумевает описание локальных процессов модели императивно. Размер кода отличается, но является не самым страшным моментом. Стрелочки на скрине показывают последовательность чтения кода при дебаге — попытке увидеть последовательность его выполнения (самая крайняя левая стрелка — начало операции). На этом примере очевидно, что процессы, описанные через реактивные интерфейсы, требуют больший путь для чтения — приходится больше прыгать глазами по коду.
В чём же причина, почему в этом примере реактивный подход выглядит так плохо? Давайте вспомним определение, что реактивность — способ разбиения кода. Зачем разбивать код единого процесса? В этом нет никакого смысла. Помимо связанности у нас есть и зацепленность, иногда эти понятия путают, но в английском они строго определены: сoupling и cohesion. На вики есть прекрасная иллюстрация.
Модули системы должны быть отвязаны друг от друга, но внутри, наоборот, они должны быть полностью сцеплены. Мне нравится другая формулировка — процессы должны быть максимально последовательны, линейны, императивны.
Чаще всего, контейнер доменной логики — это не данные, а процессы, которые описывают условия и правила их трансформации и изменения. Это самая сложная и важная часть кода приложения. Она должна описываться максимально просто.
Бывают задачи, когда части процессной логики должны быть реактивными — на них должны как-то реагировать другие модули системы и делать это прозрачно для основного домена. Например, когда нам нужно собирать аналитику с действий пользователя и системы, делать какие-то трассировки. В таких задачах событийная модель, конечно, будет правильным решением. Но по моей практике — такие сценарии встречаются очень редко. Чаще всего достаточно завернуть наши данные в интерфейс подписки, чтобы было проще соблюдать SSoT — это потребность большинства приложений.
▍ Выводы
ФРП и Rx помогают описывать асинхронные цепочки процессов, которые можно переиспользовать, но нужно это откровенно редко. При этом Rx очень плох в управлении состоянием — связанными данными. Команда Angular так и не смогла адаптировать rxjs для оптимального управления состоянием и недавно завезла отдельный примитив для этого — сигналы.
MobX отлично справляется с управлением связанными данными, но у него полностью отсутствуют примитивы для описания реактивных процессов, решающие редкие, но также важные задачи.
Оба имеют специфическое апи и связанные с этим проблемы.
Как вы понимаете, я автор ещё одной реактивной библиотеки (Reatom), рассказывающий вам об этом, как в своей реализации учёл обе эти потребности и сделал внутренние примитивы для описания процессов (экшены) тоже реактивными. Вы можете максимально эффективно описывать состояния, зависимые состояния, императивно описывать процессы и лениво, при необходимости, подписываться на них.
Выбирайте правильный тулинг, а самое главное — делайте это осознанно. Low coupling для модулей через публичные реактивные интерфейсы. Hight cohesion — через простой императивный код для внутренней логики — процессов.
- 3.1 Суть реализаций