> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 0001 — Один модуль, три точки входа, без брокера

> Почему Omniflow — модульный монолит с отдельными процессами api, bot и worker и при каком условии это перестанет быть верным

**Статус:** принято.

## Контекст

Omniflow устанавливает один оператор на один сервер, чтобы вести один
VPN-сервис. Работа, которую он выполняет, охватывает поверхности, которые
принято разделять: HTTP API, бот Telegram и фоновые задания. Разнести их по
отдельно разворачиваемым сервисам — рефлекс, и он был отвергнут.

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

Второе ограничение — кто это эксплуатирует. У целевой установки нет
платформенной команды. Три контейнера, одна база и один `.env` — это система, в
которой один человек разберётся в три часа ночи.

## Решение

Один модуль Go. Три точки входа — `cmd/api`, `cmd/bot`, `cmd/worker`, — которые
используют одни и те же доменные пакеты, одну базу данных и одну конфигурацию и
разворачиваются как три контейнера.

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

* Доменные пакеты не импортируют транспорт, генерацию доступа к базе, Telegram и
  пакеты конкретных провайдеров. Стрелки зависимостей направлены в одну сторону.
* Внешние системы находятся за небольшими интерфейсами, поэтому типы формы
  провайдера существуют ровно в одном пакете.
* Доменные события идут через транзакционный outbox. Брокер сообщений не
  вводится без записи, заменяющей эту.
* Сборка зависимостей явная: обычные конструкторы, вызываемые из каждого
  `main`, а общая для всех трёх часть — коммерческое ядро, которое принимает
  деньги и ставит выдачу доступа в очередь, — собирается один раз в
  `internal/app`, и его же использует интеграционная обвязка тестов. Никакого
  фреймворка внедрения зависимостей.

Процессы разделены потому, что у них разные характеристики отказа и
масштабирования, а не потому, что разный код. Ожидание после флуд-лимита Telegram
не должно останавливать оформление покупки; долгая сверка не должна удерживать
HTTP-воркер.

## Последствия

Перезапуск одного процесса ради изменения кода означает выкатку одной и той же
версии образа на все три, потому что это один артефакт. Выкатка исправления в API
переустанавливает и бота.

Горизонтальное масштабирование ограничено тем, что обслуживает один экземпляр
PostgreSQL. Поддерживается сколько угодно реплик `api` за прокси и сколько
угодно реплик `worker`: проходы по всей таблице — это периодические задачи
River, которые выполняются один раз за интервал на всю установку, а не один раз
на процесс. `bot` работает ровно в одном экземпляре, потому что Telegram
доставляет обновления одному потребителю. Пул подключений каждой реплики
расходует `max_connections` PostgreSQL. Истории шардирования нет и не
планируется — см. [Развёртывание](https://omniflow-vpn.mintlify.site/ru/operations/deployment.md#поддерживаемая-топология).

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

Взамен изменение, охватывающее покупку, право на услугу и выдачу доступа, — это
одна транзакция и один тест, а не распределённый протокол, а обновление для
оператора — это `docker compose pull`, а не упорядоченная выкатка.

## Когда пересматривать

* Один экземпляр PostgreSQL доказуемо является узким местом реальной установки, а
  не предполагаемой.
* Omniflow должен работать как управляющий слой над установками, которыми он не
  владеет; тогда у outbox появляется потребитель вне этого репозитория — см.
  пункты об управлении парком и NATS JetStream среди кандидатов после 1.0 в
  `ROADMAP.md`.
* Появляется возможность, которую действительно нельзя зафиксировать в одной
  транзакции с изменением состояния, которое её требует. Пока такой не было:
  ближе всех подошёл шлюз цифровых товаров, и он разрешён тем, что неоднозначная
  выдача откладывается для человека.

## Смежное

* [Обзор архитектуры](https://omniflow-vpn.mintlify.site/ru/architecture/overview.md) — процессы и путь запроса.
* [Технологический стек](https://omniflow-vpn.mintlify.site/ru/architecture/technology.md) — чего намеренно нет и почему.
* [Запись 0002](https://omniflow-vpn.mintlify.site/ru/architecture/decision-0002-postgresql-only-durability.md) — правило надёжности, на которое опирается это решение.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.