Skip to content

Идеология

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

Когда логика маленькая, почти любой подход выглядит нормально. Проблемы начинаются позже: один обработчик меняет несколько полей, другой запускает запрос, третий сбрасывает ошибку, четвертый держит loading в UI. Поведение остается рабочим, но перестает быть видимым как модель. Virentia строится вокруг обратной идеи: у каждого движения состояния должно быть место и имя.

Императивность

Императивный код удобен. Он естественно описывает “сделай это, потом это”, хорошо подходит для вызова внешних API и понятен в маленьких сценариях. Virentia не пытается запретить императивность: effect handler остается обычной async-функцией, callback из UI может вызвать событие, реакция может записать значение в стор, а scoped(scope, fn) дает обычному коду контролируемый доступ к сторам.

Проблема начинается, когда императивность становится единственным способом описания бизнес-логики. Если UI сам ставит loading, сам ловит ошибку, сам чистит результат и сам решает, какой запрос отменить, то модель размазывается по местам вызова. Такой код сложно тестировать отдельно от интерфейса, сложно переиспользовать на сервере и сложно менять без случайных регрессий.

Баланс в Virentia такой: императивный код остается на границах и внутри именованных правил, но причинность выражается через примитивы модели. Событие называет произошедшее. Реакция описывает правило. Эффект показывает жизненный цикл внешней работы. Стор хранит результат. Это не делает код “чистым” ради чистоты, но делает бизнес-логику наблюдаемой и переносимой.

Статичность

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

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

Virentia сохраняет полезную часть статичности: сторы, события, эффекты и реакции остаются явными определениями. Но значения живут в scope, а временная работа может жить внутри owner. Поэтому одна модель может быть стабильной по форме, но иметь несколько независимых запусков и нормальную очистку.

Концепты

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

Событие нужно для факта. Оно не обязано говорить, как менять поле. Хорошее событие называет произошедшее: submitted, messageReceived, queryChanged. Благодаря этому одно событие может запускать несколько правил, а модель не превращается в набор setters.

Эффект нужен для внешней асинхронной работы. Запрос, storage, worker, таймер или аналитика почти всегда имеют жизненный цикл: старт, успех, ошибка, отмена, pending-состояние. Если держать этот жизненный цикл в UI, он быстро расползается. Эффект делает его частью модели.

Реакция нужна для правила. По умолчанию Virentia рекомендует автовычисление зависимостей: реакция читает сторы, а система понимает, от чего она зависит. Это удобно для правил, которые естественно следуют из состояния. Но явный on остается важной альтернативой, когда причина запуска сама является частью смысла: конкретное событие, эффект или юнит жизненного цикла.

Scope нужен для конкретного набора значений модели. Модель описывает правила, а scope хранит состояние отдельного экземпляра приложения, запроса, теста или временного сценария. Это разделение делает изоляцию обычной частью системы, а не специальным трюком для тестов.

Владелец (owner) нужен для времени жизни. Если модель создана на время модального окна, чата или вкладки документа, вместе с ней могут появиться реакции, подписки и функции очистки. owner дает им общую границу: когда сценарий закрыт, временная работа отвязывается вместе.

Практический итог

Virentia не выбирает крайность. Она не говорит “пишите только декларативно” и не заставляет держать все в вечной статичной схеме. Она оставляет императивность там, где она полезна, сохраняет статичность там, где она дает предсказуемость, и добавляет скоупы и владельцев там, где приложение живет динамически.

Главная цель — чтобы сложная бизнес-логика оставалась моделью, а не набором случайных действий в UI, роутере и callback-функциях внешних библиотек.