Стейт-машина в Angular: учимся использовать правильно
Для начала давайте проясним, что же такое стейт-менеджеры. Если вы оказались здесь, следовательно, знаете о них хотя бы в общих чертах. Примерное описание звучит следующим образом:
Паттерн State Machine предназначен для создания объектов, поведение которых варьируется в зависимости от состояния. При этом для клиента создается впечатление, что изменился класс объекта. Таким образом, назначение предлагаемого шаблона совпадает с таковым для паттерна State, однако область применимости и степень гибкости последнего уже.
Главная идея заключается в том, что объект находится в одном из нескольких сменяемых состояний и может по-разному реагировать на одни и те же события, которые с ним происходят. Набор этих состояний и переходов между ними предопределён и конечен.
Больше материала по теме читайте тут. Это старая статья, но, кроме интересного паттерна в ней прилагается список литературы о конечных автоматах и управлению состояниями в инженерном деле и математике.
Как используют стейт-машины
Сфера применения стейт-машин специфична — это переключатели, реализующие смену состояний, например, в играх, или управляющие переключением различных механизмов программ, например коннект или дисконнект соединения с сетью. Кроме этого, стейт-менеджеры используют, например, для управления состоянием сети блокчейн.
На фронтенде их регулярно используют в качестве хранилища данных с CRUD’ом и шиной событий для относительно безопасного общения частей приложения между собой, для хранения токена или данных пользователя с доступом к ним во всём приложении, или всевозможных списков (страны, языки, валюты и прочее). Это не всегда соответствует области применения стейт машин и в таких случаях целесообразнее использовать более подходящие инструменты.
Плюсы и минусы
Итак, давайте рассмотрим преимущества и недостатки использования стейт-машин в контексте фронтенд-приложений.
Какие мы получаем преимущества:
- хранение дерева состояний с возможностью передвигаться по нему;
- единый источник достоверных данных;
- условно безопасное сообщение между частями приложения;
- однообразность в реализации;
- легкость в тестировании.
Теперь давайте посмотрим на проблемы, которые мы приобретаем взамен:
- бойлерплейт, зачастую превышающий код самого приложения и необходимость его поддерживать наравне с бизнес-логикой;
- слабую производительность при больших объемах информации;
- увеличение сложности, которую приносят специфичные паттерны стейт-менеджера (часто к нему обращаются с целью понизить сложность кода, но, как правило, она только возрастает);
- связанную архитектуру, что влечет за собой удорожание фич, сложность исправления багов, незапланированные рефакторинги и т.д.
Ну хорошо. Так как же правильно использовать стейт-менеджер?
В первую очередь нужно уяснить, что механизм управления состояниями становится ядром вашего приложения. Не программа его использует, а наоборот — он пускает корни в приложение и паразитирует на его ресурсах. Из этого следует вывод — вы связаны архитектурой, которую навязывает стейт-машина, подходит она для вашей бизнес-логики или нет. Критически важно определить степень ее необходимости в проекте еще на начальном этапе, иначе впоследствии вы будете страдать на сопровождении или удалять ее из уже готового кода, как сорняк, что дорого и тяжело, вплоть до переписывания всего проекта целиком.
Далее перед тем, как писать код, проанализируйте требования к проекту, взвесьте все за и против, определите действительно ли вашему проекту требуется именно управление состояниями. Важно так же учитывать целесообразность — область применения менеджеров состояний довольно специфична и не широка, как это можно понять из описания в начале статьи. Чаще, требуется только Store или Event Bus, альтернатив которым полно в сети или которые можно легко написать самостоятельно, и они не будут диктовать условия вашей архитектуре, но гармонично в неё впишутся.
Теперь рассмотрим случаи использования стейт-машин, в которых они действительно будут кстати. Это уместно, например, в знаменитом пасьянсе «Косынка», где состояние изменяется и фиксируется каждый раз, когда карта перекладывается на новое место. Или в игре в кости, где у кубика 6 граней, а с программной точки зрения 6 состояний. Так же стейт-машина полезна в случаях, когда предполагается большое количество состояний и множество триггеров, чтобы эти состояния менять. Здесь без стейт-машины сложно управлять изменениями и сохранять их чистыми.
Если не требуется управлять состояниями, использование стейт-менеджера усложнит ваше приложение, не давая ничего, что нельзя получить другими способами.
Перенесемся на момент в мир векторной иллюстрации. На картинке изображены два практически одинаковых шара, но с одним большим «НО» — оба шара созданы разными способами.
Левый шар состоит из плохо редактируемой сетки, правый же представляет из себя простую фигуру, остается гибким и легко поддается редактированию. Посмотрим, что «под капотом» у обоих:
Инструмент, использованный слева, безусловно, полезен — он подходит для весьма специфичных задач, где тяжело обойтись более простыми средствами, но если ваш проект представляет из себя простой шар, то нет необходимости использовать сложные решения для работы с ним, в том числе и на начальном этапе, потому как работать вам с ним здесь и сейчас. Если же говорить о масштабируемости, то закладывать на перспективу роста нужно архитектуру, а не готовые решения.
Вам никогда не покупали одежду на вырост?
Случалось ли такое, что на момент, когда она становилась впору, вещь уже становилась неактуальной?
Так же может случиться и с вашим проектом — использование избыточных технологий только ради абстрактного роста неоправданно, т.к. есть риск стать заложником выбранного решения, которое в будущем окажется неуместным или неподходящим. Не используйте тяжелую махину ради пары плюшек.
Чем заменить Store?
В большинстве стандартных случаев, для хранения простых данных, подходит старый добрый localStorage, или ngx-store. Для сложных случаев подойдут: Dexie.js, LokiJs, sql.js, Alasql и другие. Или, как вариант, напишите собственный стор — это не так сложно, как может показаться на первый взгляд.
Чем заменить шину событий?
Простой и надёжной шиной сообщений являются сервисы, организованные, как рекомендовано в CQS/CQRS. Есть и готовые решения — беглый поиск по github выводит ngx-message-bus и angular-cqrs. Возможно найдете что-нибудь лучше, пишите в комменты, с удовольствием добавлю новые ссылки.
Итак, рассмотрев варианты, вернемся же к главному вопросу: как правильно использовать стейт-менеджер?
В большинстве случаев правильным ответом будет — «никак». При условии, если ваш проект в этом действительно не нуждается.
Хайп вокруг темы и разнообразие предлагаемых готовых решений вводят в заблуждение по поводу необходимости стейт-машин. Складывается ошибочное мнение об их важности в каждом фронтенд-проекте. Бойлерплейт, который необходим им для работы воспринимается, как неизбежное зло, а не воспалённый аппендикс в приложении, требующий немедленного удаления. Авторы некоторых популярных решений по управлению состояниями создали схематики для генерирования бойлерплейта, но в конечном счёте это мало что решает, т.к. написать код — 20% усилий и времени, а сопровождать его — остальные 80%. Они автоматизировали создание гор непонятного мусора, который вы обязаны будете поддерживать руками.
В заключение
Всё вышесказанное является моим личным мнением, основанным, в том числе, на длительном опыте работы с сабжем. Я не противник использования стейт-менеджеров, но считаю, что каждый инструмент должен использоваться по назначению.
Если захотите дополнить список альтернатив или же просто пообщаться, стучитесь ко мне в телеграм:
Также заходите к нам в группы: