Жил-был разработчик

Жил-был разработчик. Работал на Unity. Любил свою работу.

Разработчик любил архитектуру. Поэтому подключил DI-контейнер. Потом второй, потому что в первом не было ScriptableObject-биндингов. Потом третий, потому что во втором не работали async scope. Везде была фабрика фабрик, IServiceProvider, который под капотом резолвил IServiceProviderFactory, и пять способов сконфигурировать один и тот же InventoryService.

Разработчик любил чистый код. Поэтому развёл IInventoryService, IInventoryRepository, IInventoryFacade, InventoryDTO, InventoryMapper, InventoryValidator и InventoryQueryHandler. Семь классов, чтобы положить в инвентарь меч. Меч был один.

Разработчик любил тестируемость. Поэтому каждый класс брал в конструктор шесть интерфейсов. Когда геймдизайнер сказал «добавь параметр количества», пришлось пройти восемнадцать слоёв и обновить четыре регистрации контейнера.

Разработчик устал.

И написал свой фреймворк.

Это не статья про конкретные техники — они описаны в документации, ссылки в конце. Это статья про принципы, из которых эти техники следуют. И про то, почему именно такие принципы. Их пять.

Принцип 1. Технология должна окупаться

Сквозное правило отбора любой технологии во фреймворке: стоимость её внедрения и поддержания должна быть меньше профита, который она даёт.

Не «архитектурно правильно». Не «как делают в индустрии». Не «это best practice». Не «канонический паттерн». А конкретный, измеримый, чистый выигрыш на каждой задаче, в каждом use case.

Этот фильтр отсекает большую часть «канонической архитектурной красоты», которая в реальной разработке съедает больше, чем приносит. Семь интерфейсов вокруг одного меча - это и есть отрицательный баланс: стоимость поддержки растёт, а реального выигрыша от подменяемости семь раз нет, потому что меч - один.

Это банальный, кажется, принцип. Но именно он первым сдаётся в проектах, где «правильно» становится важнее «полезно». Vortex держит этот принцип явным правилом: технология попадает во фреймворк, только если её цена меньше того, что она даёт. Иначе - не попадает, даже если она «правильная».

Принцип 2. Учебник - это хорошо, но за костыли бьют не по учебнику

DI решает реальную задачу: «как класс получает зависимости в системе со сменными сервисами, разными scope’ами и несколькими instance’ами одного контракта». Это реальность backend-сервиса: сотни типов запросов, request / session / singleton scope’ы, реальная подмена реализаций под разные среды, разные тенанты.

Unity-клиент устроен иначе:

  • 90% состояния глобально по природе (инвентарь, настройки, прогресс, текущий уровень)

  • 90% подсистем стабильны на всё время игры (от Application.Start до Application.Quit)

  • 90% инстанцирования идёт через инспектор, а не через код

Делать вид, что InventoryController - это сменный сервис, который можно перерегистрировать в рантайме - самообман. Он не сменный. Он один. На всю игру.

Vortex строится из того, что реально есть в Unity-клиенте: фиксированный набор подсистем, статическая структура зависимостей, инспектор как основной канал композиции, ScriptableObject как основной формат данных, MonoBehaviour как основная единица сцены. Не вокруг идеального мира с request scope’ами и interchangeable сервисами - а вокруг этой среды.

Если ваш рантайм устроен так же - отказ от backend-абстракций уберёт значительную часть бойлерплейта без потери возможностей. Если иначе (server-authoritative, headless, multi-tenancy) - вам нужен другой инструмент. Принцип 1 в действии.

Принцип 3. Верстка как программирование

В Unity-проекте большая часть production-работы - визуальная. Геймдизайнер собирает квест. Художник назначает анимации. Продюсер выставляет баланс. Левел-дизайнер расставляет триггеры. Они не пишут код. Они открывают инспектор.

Если архитектура заставляет каждое такое изменение проходить через программиста — она не подходит к Unity, как бы хороша ни была сама по себе. Не «плохая» - просто из другой среды, где content-pipeline идёт через JSON, миграции, deploy.

Vortex проектируется вокруг визуальной композиции. Цепочки логики квестов, конфиги подсистем, выбор драйверов, привязки UI, баланс - всё в инспекторе. Полиморфные [SerializeReference]-структуры, кастомные атрибуты для фильтрации и выбора, типизированные picker’ы по базам данных, drawer’ы для отображения long как даты или таймера - не дополнение, а основной production-канал.

Цена этого - жёсткая зависимость от Odin Inspector. ~$55 за seat, проприетарный, потенциально политически неприемлемый. Сознательный выбор: написать собственный аналог можно, но если Один уже есть в проекте - они подерутся. А делать библиотечную шизофрению на условных ключах - необоснованный мусор в коде. Принцип 1 фильтрует и эту цену.

Принцип 4. Структура должна быть видна

В DI-проекте dependency graph существует - но размазан между конструкторами, регистрациями, фабриками, scope’ами. Компилятор знает, что от чего зависит. Человек, открывший проект - нет, пока не оттрассирует bootstrap.

Vortex переносит структуру туда, где её видно глазами и где её проверяет компилятор:

  • Слои разделены через asmdef. Core → Unity → Sdk → AppLocale, обратное направление запрещено физически. Это не соглашение, не review-правило, не строчка в стайлгайде - это компилятор отказывается линковать. Самая надёжная проверка из возможных.

  • Подсистемы - статические шины с явными именами. Inventory, Database, QuestController. Кто чем пользуется - видно по using’ам и по asmdef-references. Не «угадай по [Inject]-параметрам, какой контейнер их резолвит».

  • Сборка приложения - конфигурация в ScriptableObject. Не services.AddSingleton<IFoo, FooImpl>() в bootstrap-классе, а список драйверов и пакетов в инспекторе. «Из чего состоит приложение» - это галочки в Project Settings, а не код в Composition Root, который надо читать.

Цена: dependency graph теперь не в конструкторах класса, а в asmdef-references и в коде. Новичку нужна карта шин. Без неё утонет. Не утверждаем, что цена маленькая, но и огромной - не назвать.

Профит: компилятор проверяет архитектуру. Структура видна на уровне файлов и папок. Чтобы понять состав приложения - не надо запускать debug-сессию.

Принцип 5. Каждому шурупу - свой молоток

Есть старый анекдот про бюрократов которые требуют чтобы мироздание было приведено к бумажным стандартам, но ни в коем случае наоборот... Ничего не напоминает? А если посмотреть на архитектурно чистый DI в Unity проекте?

Vortex - специализированный инструмент. Не серебряная пуля. Не «правильная архитектура для всего».

Он эффективен, когда:

  • рантайм - Unity-клиент с фиксированной структурой подсистем

  • production-канал включает дизайнеров и художников, работающих через инспектор

  • команда готова разделить дисциплину архитектурного канона

  • Odin Inspector допустим как зависимость

Он неэффективен или вреден, когда:

  • рантайм - backend, headless, server-authoritative с in-process multi-tenancy

  • проект - микропрототип на 1–2 экрана, не окупающий оверхед инфраструктуры

  • compile-time safety важнее workflow-скорости (нет shared review-дисциплины)

  • Odin Inspector нельзя по политике, лицензии или убеждениям

  • код должен работать вне Unity (CLI, WebAssembly, headless-симуляция)

Это не маркетинговая оговорка ради приличия. Если ваш профиль попадает в зону «не для этого» - Vortex даст результат хуже, чем DI или любой другой подходящий инструмент. Используйте то, что соответствует вашему рантайму, а не то, что красивее звучит на конференции.

Это принцип 2 в обратную сторону: инструмент подбирается под среду, а не среда переделывается под инструмент. Если у среды и инструмента разная природа - на стыке всегда будет натирать, и натирать будет именно ту команду, которая взяла «модно».

Что дальше

Конкретные техники - owner-key для capability-based мутации, реактивные значения, логические цепочки, статические шины подсистем, layered asmdef, ScriptableObject-композиция, драйверная модель - это следствия принципов, а не их источник. Каждое решение во фреймворке проходит фильтр принципа 1. Каждое объяснимо через один из остальных четырёх.

Если принципы зашли - в документации это разобрано подробно по топикам:

Если не зашли - Vortex не для вас, и это нормально. Принцип 5.

Эпилог

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

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

Комментарии (3)


  1. rbdr
    24.05.2026 08:36

    А чего, итальянские кухни вполне себе ого-го!


  1. hermer29
    24.05.2026 08:36

    Пожалуй описанная проблема — абстрактная. Когда решаешь проблемы архитектуры в начале жизни проекта ты как раз и думаешь как сделать так чтобы потом ноги не сломать и не было необходимости в куче формальностей. Тут явно KISS помогает добиться простоты поддержки, т.к. ты не наделаешь сложных структур классов. По сути упрощает задачу поддержки через минимизацию информации которую надо знать. Плюс если твои решения идиоматичные то описанных проблем как раз не будет, потому что проекты подобные твоему уже делали определенным образом


    1. RexWolf Автор
      24.05.2026 08:36

      Все правильно сказали. Есть правила, есть принципы... примеры есть... Остается только вопрос: откуда берется спагетти в репозитории?