Современные приложения становятся все более сложными и многокомпонентными. Если говорить о приложениях с фронтом на Angular, то при разработке постоянно возникает задача: как хранить и управлять состоянием приложения, чтобы в любой момент при доступе из любого компонента обрабатывались актуальные данные без ошибок.
Раньше у нас было два варианта ее решения:
Использовать самописные компоненты — не самый удобный подход, так как каждый раз нужно тратить время на их проектирование, разработку и тестирование.
Или применять готовые компоненты, такие как NgRx. С ним тоже все было не слишком гладко: сервис громоздкий, имеет высокий порог вхождения. Используя впервые, с ним приходится долго разбираться, осваивать длинные запросы, при том что документация не очень подробна. Все это создавало лишние сложности и для тех, кто уже с ним работает, и для новых участников проекта.
На одном из недавних проектов мы перешли на сервис управления состоянием Akita — и это точно было удачное решение. Почему?
В первую очередь — он простой и удобный. Быстро интегрируется, обеспечен краткой, но достаточной документацией. Хорошо поддерживается: разработчики регулярно фиксят ошибки, отвечают на вопросы.
Система построена на RxJS, который массово использовался в проекте, поэтому для нас не возникло никаких сложностей.
Косвенный, но немаловажный аргумент: изначально Datorama разработала сервис для собственного использования — а потому он сделан качественно. И затем, когда отлично себя показал, его выложили в бесплатный общий доступ.
Как работает Akita
В Akita три основных компонента.
- Хранилища Akita Store
Есть хранилища двух типов:
- базовое, которое можно использовать для хранения сессий юзера,
- хранилище сущностей, которое обеспечивает удобное хранение массива объектов конкретного класса, например, категорий пользователей.
Хранилище создается буквально десятком строк. В папке компонента создаем папку State, кладем туда файл, в котором State реализуется. Он расширяет EntityState из Akita, мы указываем для него ключ — и получаем готовое хранилище, реализующее нужные методы для работы с данными.
2. Запросы Akita Query
Для запросов в хранилище используются расширения Query. Для начала использования Store не требуется создавать методы для выборки данных: просто создаем класс, расширяющий Query из Akita, и указываем в нем требуемое хранилище. После чего получаем доступ к встроенным методам для работы с данными в хранилище.
Базовые классы содержат часто используемые методы: положить в хранилище, выбрать из хранилища. Например, в классе QueryEntity есть метод Select, который возвращает из хранилища то, что нам нужно, с помощью лямбда-выражения, и SelectAll, возвращающий все записи.
Набор методов можно расширить. Допустим, нужно выбрать юзеров, у которых есть какое-либо право доступа — внутри класса Query создаем метод getUsersWithPermission с указанием параметра разрешения. И возвращаем из Store юзеров, соответствующих параметру.
В результате расширение кода производится быстрее и проще, чем в NgRx.
3. Сервис и методы
Данные в хранилище мы не модифицируем напрямую. Akita реализует принцип “Single source of truth”: актуальные корректные данные лежат в одном месте, там же модифицируются, все компоненты получают их из одного источника. Значит, никакой компонент не может изменить у себя данные так, что другой об этом не узнает.
При открытии страницы компонент в сервисе вызывает бэкэнд и достает данные, мы кладем их в Store. Для этого есть сервис, в котором используются специальные методы, которые модифицируют набор данных в хранилище.
Например, метод Set полностью замещает все данные, которые хранятся в Store. Update может обновить даже единичную запись. Upsert обновит запись, если она есть в Store, и добавит, если ее нет.
Удобна встроенная поддержка отслеживания состояния загрузки с использованием свойства loading. Ставим { loading: true }, на странице значение свойства проверяется и отображается прогресс-бар.
Метод withTransaction поддерживает взаимодействие компонентов, использование одних Store в других и одних Query в других. Например, если нескольким юзерам из таблицы присвоили несколько новых территорий — нужно обновить Store с юзерами и с территориями. Внутри транзакций мы обновляем оба Store, при этом никакие данные не потеряются.
Почему стоит выбрать Akita
Упрощается процесс разработки
Важные преимущества для разработчиков — низкий порог вхождения, хорошая документация и поддержка. И на сайте самого сервиса, и на GitHub есть достаточно информации, чтобы снять большинство вопросов. Если вопросы остаются — поддержка отвечает, помогает разобраться в проблеме.
Увеличивается скорость разработки
Написание нового кода и расширение имеющегося ускоряется в разы. Для запуска нужно создать три небольших файла: хранилище, сервис и запросы. Базовая функциональность создается очень быстро и может использоваться между компонентами: например, положить в хранилище lookup таблицу, чтобы один раз при загрузке страницы достать из базы.
Расширение функциональности также намного упрощается. Сервисы и Query уже есть, если у разработчика появляется специфическая задача — ему достаточно прописать единственный метод в уже существующем файле Query.
Стоит добавить, что чем выше скорость — тем меньше стоимость разработки.
Уменьшается количество ошибок
Несмотря на то, что файлов становится больше, за счет четкой структуры процессы упрощаются: легко отследить, где лежит и используется любой компонент, как они взаимодействуют между собой.
Кроме того, мы получаем простой способ реюзабилити: все методы для работы с определенным хранилищем собраны в одном месте, и мы можем использовать их в любом другом компоненте.
А дальше можно говорить о преимуществах более высокого уровня. Выше скорость обновлений, меньше багов — приложение становится более популярным, больше нравится пользователям и приносит больше денег.
Таким образом, если посмотреть более глобально, то использование Akita дает выгоды всем сторонам, включая конечного заказчика.