Стейт-машина в Angular: учимся использовать правильно

Alex Dukhnovskiy
Angular Soviet
Published in
5 min readMar 19, 2019

--

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

Паттерн 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%. Они автоматизировали создание гор непонятного мусора, который вы обязаны будете поддерживать руками.

В заключение

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

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

https://t.me/AlexDaSoul

Также заходите к нам в группы:

https://t.me/ngSoviet

https://t.me/angular_ru

--

--