Артем Арутюнян

Артем Арутюнян

Темы
Неделя
Oct 14, 2019 → Oct 17, 2019

Архив недели @artalar_dev

Понедельник


Всееем привет! С вами @art_al_ar: тимлид в @dbeaver_news. На этой неделе, в основном, будем говорить о стейт-менеджерах (СТМ) - оказывается в них есть множество неочевидных нюансов.

Немного обо мне: - лет 7 в ИТ, программирую года 3, все вокруг веба, преимущественно финтех - интересуюсь очень много чем, от UX до компиляторов, во всем поспел, ни в чем не преуспел Христианин, муж, программист, бушкрафтер.

Так же я веду t.me/web_conf_repor… где стараюсь публиковать все записи докладов по вебу и вообще стараюсь помогать @csssr_dev делать онлайн-школу. Еще выступаю с докладами: youtube.com/playlist?list=…

Reatom - deterministic state manager! Весьма многообещающий проект, менеджер состояний с первоклассным выводом типов на TypeScript, маленьким размером бандла, учитывающий и переосмысливший недостатки других популярных библиотек, в первую очередь Redux: soundcloud.com/5minreact/061-…
Совсем вкратце (запись длится час и это только первая часть) с темой недельного топика можно ознакомится вот так: twitter.com/5minreact/stat…

Я свой СТМ написал с 8ой попытки, а уж сколько названий пришлось перебрать и с каким трудом - жуть. Вот тут есть страшная история про выбор имени пакета для npm: npmjs.com/package/awful-… Но еще сложнее, конечно, сделать хорошее лого. Благо реатому в этом помог @belozyorcev 🙏 twitter.com/rage_monk/stat…

Коллеги, я бы хотел плавно войти в тему в общем и не фокусироваться только на reatom, но т.к. уже начали поступать конкретные вопросы, немного спойлерну. На этой неделе я бы хотел, вместе с вами, начать формировать формальную спецификацию стейт-менеджераgithub.com/artalar/state-…

Каким требованиям должен удовлетворять стейт-менеджер (СТМ) и вариативность каждого требования. Сравнительная таблица и тесты разных, популярных и не очень, СТМ.

Что такое стейт-менеджер (СТМ), кто-нибудь видел определение? Откуда этот термин вообще взялся и за что отвечает? (я не знаю, буду благодарен если поделитесь) Но, давайте попробуем описать свойства СТМ и вывести из них определение.

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

Это описание может быть реализовано разными способами: - прямой доступ к данным в представлении ЯП (MobX) - собственный язык запросов (?Datalog) - предподготовленные запросы (похоже на хранимые процедуры)

предподготовленные запросы можно разделить на: - курсор (ну например github.com/artalar/pathon) - линза (github.com/calmm-js/kefir…) - метод (ООП) - action (Flux) - таблица переходов (конечный автомат).

(способы переходов отсортированы от императивщены к декларативщене)

Но это еще не все, практика подсказывает, что, помимо чтения и записи, необходимо реагировать на изменения и кмк именно тут начинается главная работа СТМ.

Дело в том что самое сложное - это именно определение того что изменилось. Часто, в зависимости от того каким образом автор СТМ решил определять изменения, будет выбран тот или иной тип описания изменений - это очень связанные вещи (хотя и не абсолютно).

Типы определения изменений: - паттерн заместитель (прокси и геттеры с сетерами) для перехвата каждой записи - подписка на предподготовленные запросы - сравнение результата (иммутабельность) (возможно я еще что-то не учел)

При этом библиотека может использовать те или иные типы в разных комбинациях или как-то частично. В рамках твиттера все это расписать, конечно, невозможно, приведу лишь список встретившихся мне СТМ (все они достаточно оригинальны): gist.github.com/artalar/e5e8a7…

Выше я вкратце, но на сколько возможно полно, описал то с чего СТМ начинается. Кажется, все уже достаточно не просто, а это лишь совсем поверхностная теория. Но давайте подведем какой-то итог.

Стейт-менеджер (СТМ) - это реактивная база данных.

Может быть не совсем прямолинейный вывод, но в рамках твиттера остановимся на этом 👀

🔥Тред (@art_al_ar)

Среда


Ааа сегодня мы разберем зачем мемоизировать селекторы редакса и почему автору стейт-менеджера (СТМ) хорошо бы знать виды графов и алгоритмы на них.

Давайте представим следующую модель данных, в корзине товаров у нас отображаются: - цена - сумма налога (зависит от цены) - общая стоимость (зависит от цены и налога) (пример максимально простой намеренно, в реальном приложении описанные ниже проблемы будут серьёзнее)
notion image

Все это, конечно, должно быть связано реактивно, как показано на рисунке (push-подход) - update обновляет только price, дальше изменения "растекаются" автоматически. Иначе, обновляя каждое значение императивно внутри update (pull-подход) мы сильно повышаем зацепленность.

Цветами разделены необходимые этапы, выполнение которых должно произойти при поступлении нового значения: 1. (зеленый) - установка новых значений 2. (желтый) - вычисление зависимых значений 3. (красный) - вызов сайд-эффектов

Классические реактивные подходы основанные на потоках (observable) с которым можно познакомится тут habr.com/ru/post/279715/ или подробнее тут github.com/kriskowal/gtor

Но с этим стандартным и столь распространенным подходом есть достаточно серьёзная проблема - glitches, одна сторона которого подробно описана тут staltz.com/rx-glitches-ar… Я бы, вкратце, описал это так: обход графа подписок осуществляется в глубину, а надо бы в ширину.

На нашем примере стандартная последовательность оповещения observable будет выглядеть так: update(1) -> price(1) -> tax(1) -> cost(1) -> cost subscriber(1) -> tax subscriber(1) -> cost(2) -> cost subscriber(2) -> price subscriber(1) -> update subscriber(1) ...
notion image

И тут мы видим что "cost" и "cost subscriber" были вызваны дважды, что содержит целых две проблемы: лишние вычисления и в первом случае данные были неконсистентны, потому что cost содержал новое значение tax, но старое значение price.

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

Правильный порядок обхода подписок должен был бы выглядеть так: update -> price -> tax -> cost (сразу с новыми значениями price и cost) -> update subscriber -> price subscriber -> tax subscriber -> cost subscriber ->

Что бы достичь этого следует понимать что все зависимости - это определенный граф (DAG чаще всего en.wikipedia.org/wiki/Directed_…) и что его обход должен осуществляться в ширину (как описано в сообщении выше).

Либо обход, все же, может осуществляться в глубину, но со стороны subscribers (что бы контролировать получение, а не распространение, которое заранее не известно) и использованием кеширования (привет reselect).

Вот тут есть немного более детальное сравнение (в тестах) эффектора, мобыкса и Rx codesandbox.io/s/effector-com…

🔥Тред (@art_al_ar)

Четверг


На нашем примере стандартная последовательность оповещения observable будет выглядеть так: update(1) -> price(1) -> tax(1) -> cost(1) -> cost subscriber(1) -> tax subscriber(1) -> cost(2) -> cost subscriber(2) -> price subscriber(1) -> update subscriber(1) ... pic.twitter.com/mMnQrlVX2S
С формальной точки зрения описанная проблема называется проблемой ромбовидных зависимостей (eng. diamond problem) и происходит она когда наш граф зависимостей перестает быть деревом, потом что его ветви сходятся обратно (по направлению их движения). twitter.com/jsunderhood/st…

Я, к сожалению, не знаю литературы introduction уровня в которой бы это хорошо описывалось. Т.е. есть какие-то пейперы (которые я не читал) или университетские курсы, но нет доступных статей прикладного характера.

К счастью @theKashey только что опубликовал свой материал с разбором на конкретной практике, за что ему огромное спасибо: dev.to/thekashey/deja…

@jsunderhood Автор затронул хорошую тему, но почему-то не упомянул инструменты, с помощью которых можно реализовывать селекторы. Я юзаю reselect. github.com/reduxjs/resele…
Упомянул :) twitter.com/jsunderhood/st… pull подход становится все менее эффективным и даже накладным с ростом количества подписчиков, т.к. каждому нужно, как минимум обратиться, к кешу. Но инициализация обработчиков будет быстрая, это плюс. twitter.com/fake_sheeran/s…

При этом в push подходе все наоборот: мы медленно инициализируемся перестраивая (сортируя) граф под каждый апдейт, зато при поступлении апдейта проходимся только и только по нужным зависимостям. Мне такой подход в общем кажется предпочтительней.

В продолжении треда @roman01la MobX вообще хитро работает: он подписывается (push) только на то чей кеш был инвалидирован во время pull обхода. За счет этого достигается одновременно и реактивность и ленивость!

О, что-то свежее (и большое) про react zombie children kaihao.dev/posts/Stale-pr… Это крайне не приятная проблема появляющаяся если мы создаем какие-то вычисления в динамике основываясь на динамических данных.

Немного сложно объяснять, почитайте статью, или посмотрите вот этот доклад: youtube.com/watch?v=cRtAjT… Там достаточно глубоко разобраны и преимущества и недостатки(!) хуков и про то что react zombie children никуда не делись (если вы не используете нативный контекст).

Пришло время обсудить обработку ошибок 🔥🧨🎆🌋☄️🌡️ Я еще в начале недели кидал ссылку в которой описаны некоторые идеи: github.com/artalar/state-… Еще вы можете послушать об этом в сегодняшнем выпуске @5minreact: soundcloud.com/5minreact/062-…

Вернемся к нашей корзине с товарами и подумаем где могут возникнуть ошибки при работе стейт-менеджера (СТМ) и что с ними нужно в этом случае делать: 1, 2) при работе с данными 3) при вызове сайд-эффектов
notion image

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

Ну т.е. все сложно... Вообще лучше всего когда в СТМ по умолчанию заложена такая логика обработки ошибок, которая наименьшим образом сможет создать сайд-эффект для пользовательского кода, т.е. это стремление к идемпотентности - ru.wikipedia.org/wiki/Идемпотен…

Что такое сайд-эффект? Это какой-то эффект, который находится в стороне, в не ореола нашего контроля. Сайд-эффект - это выход из зоны ответственности, порождение хаоса, чего-то безконтрольного, непредсказуемого... Звучит не очень для конечного продукта, который приносит деньги?

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

Но нам необходимо рассмотреть зону ответственности СТМ, который, конечно, вообще ничего не знает про свое окружение и в каком он там приложении и как используется. Область ответственности СТМ достаточно скромна: обновить данные, вызвать подписчиков.

Соответственно какое есть влияние у СТМ на окружающую среду (я не про патчинг протатипов 😁)? - Состояние и подписчики. Когда разработчик проектирует какую-то логику он ожидает что при определенном изменении состояния вызовутся определенные подписчики - так работает СТМ.

Причем в реальном приложении связей "изменение - вызов подписчика" так много, что проконтролировать каждое и, возвращаясь к теме треда, обработать каждое возможное исключение - нереально! Потому что исключение может наступить в случайной часте связи и это комбинаторный взрыв!

В итоге, СТМ имеет возможность внести непредсказуемое поведение в приложение двумя способами: изменить состояние не полностью (ошибка в 1, 2), вызвать не всех подписчиков (ошибка в 3).

Что бы избежать неконсистентности данных для иммутабельной модели - необходимо использовать rollback'и, либо использовать иммутабельную модель данных - но и это не панацея.

У протестированных мной СТМ: redux, MobX, effector есть одна и та же проблема - возможны ситуации когда состояние измениться лишь частично. И это точно является не верным поведением: en.wikipedia.org/wiki/Atomicity…

Reatom задизайнен так, что там никогда не произойдет частичного изменения состояния: либо полное изменение соответствующее подпискам на апдейт (экшен), либо никакого (throw). Хотя, справедливости ради, в селекторах useAtom (react) это не гарантируется из-за zombie children.

При этом, подчеркиваю, я описал необходимое поведение по умолчанию. В идеале библиотека должна так же предоставлять какие-то средства обработки ошибок. В том же effector что-то такое есть. В Reatom этого нет, что бы не создавалась иллюзия полного контроля - спорный вопрос 🤷‍♂️.

Выводы такие: стейт-менеджемент - это сложно, имеется множество подводных камней (мы разобрали только часть) с которыми можно никогда не столкнуться или столкнуться в самый неподходящий момент. Изучайте ваши инструменты лучше и требуйте этого от разработчиков (документация).

🔥Тред (@art_al_ar)

Ссылки