Как устроена
Страница для того, кто будет это ставить, обновлять и чинить. Она объясняет, из каких частей собрана платформа и почему части именно такие — потому что от этого зависит, что вы увидите на сервере и что придётся делать при обновлении.
Три процесса, а не двадцать #
Приложение одно, внутри разделено на модули с явными границами. Это модульный монолит, а не микросервисы, и выбор сознательный: микросервисы решают проблему «много команд мешают друг другу», а добавляют сетевые вызовы, распределённые транзакции и пять способов выкатить неправильно.
На сервере после установки работает:
| Процесс | Что делает |
|---|---|
| app | Веб-интерфейс — админка, кабинет ученика, публичные страницы — и HTTP-API. Только быстрые операции |
| worker | Очереди: рассылки, шаги сценариев, исходящие вебхуки, отложенные операции, генерация файлов |
| bot | Telegram-бот. Отдельный процесс, потому что держит долгоживущее соединение |
Плюс PostgreSQL и Redis, плюс обратный прокси, который занимается TLS.
Всё, что дольше примерно двух секунд, уходит в очередь. Поэтому рассылка на десять тысяч человек не блокирует админку, а перезапуск сервера не теряет запущенные сценарии: шаг сценария — это отложенная задача в очереди, а не таймер в памяти.
Воркер масштабируется отдельно: если очередь не успевает, запускается ещё один.
Модули не лезут в таблицы друг друга #
Восемь модулей: обучение, коммерция, CRM, сообщения, сценарии, вебинары, сайт, аналитика. Плюс ядро (вход, права, пользователи, настройки, события) и интеграции.
Между собой они общаются событиями, а не прямыми вызовами. Оплата прошла —
модуль коммерции публикует payment.succeeded и на этом заканчивает. Дальше:
payment.succeeded
├─ commerce закрывает заказ, создаёт покупки
├─ learning открывает доступ к курсу
├─ messaging письмо «доступ открыт» и сообщение в Telegram
├─ automation запускает сценарии с триггером «оплата»
├─ crm пересчитывает сегменты
├─ integrations шлёт исходящий вебхук вам
└─ analytics фиксирует событие для отчётов
Модуль коммерции ничего не знает ни про Telegram, ни про сценарии. Для вас это значит одну практическую вещь: новая интеграция подписывается на событие и не требует правок в продукте.
Событие пишется в базу, а не в очередь #
Наивная схема «записали в базу, поставили задачу в очередь» ломается — и ломается на деньгах. Транзакция прошла, а постановка задачи упала: оплата есть, доступа нет. Или наоборот: задача поставилась, транзакция откатилась — доступ выдан за неоплаченный заказ.
Поэтому событие записывается в ту же транзакцию, что и бизнес-операция, в таблицу
Event, а отдельный воркер публикует его в очередь. Либо коммитится всё, либо ничего.
Из этого следуют две вещи, которые видны снаружи:
Есть полный журнал того, что происходило. Не только «что сейчас», но и «что менялось» — по нему отлаживаются сценарии и переигрываются пропущенные вебхуки.
Порядок доставки не гарантируется. Даже по одному объекту. Это честное ограничение любой такой системы, и лучше сказать о нём прямо. Что из этого следует получателю вебхука:
- обработчик обязан быть идемпотентным по идентификатору события: одно и то же событие может прийти дважды;
- не полагайтесь на «предыдущее событие уже обработано» — читайте текущее состояние объекта, а не достраивайте его из истории;
- упорядочить события можно самому: у каждого есть время создания и монотонный номер.
Внешние сервисы — только через адаптеры #
Платежи, фискализация, сообщения, видео, хранилище файлов подключаются адаптерами. Ни один модуль не вызывает ЮKassa или Telegram напрямую.
Что это даёт школе: замена провайдера не переписывает продукт. Сменили платёжного провайдера — поменялся один адаптер, а заказы, доступы, сценарии и отчёты остались как были.
Оно же объясняет неочевидное: у разных провайдеров разная модель доверия. У ЮKassa подписи уведомлений нет вообще — проверка по адресам плюс обязательный повторный запрос статуса; у Продамуса — подпись HMAC. Поэтому итоговое состояние платежа всегда берётся повторным запросом к провайдеру, а не из тела уведомления: тело можно подделать, и оно может устареть.
Что нужно от сервера #
| Минимум | Комфортно | |
|---|---|---|
| Ядра | 2 | 4 |
| Память | 4 ГБ | 8 ГБ |
| Свободный диск | 20 ГБ | 40 ГБ |
| ОС | Ubuntu 22.04 / 24.04, Debian 12 | — |
| Docker | 24+ с плагином compose v2 | 29+ |
Память здесь — про сборку, а не про работу. Работающая установка укладывается примерно в 1,5 ГБ. Но сборка образа съедает 2–3 ГБ разом: если на сервере уже что-то живёт, сборка на нём — самый быстрый способ уронить соседей. Собирайте образ на другой машине и переносите готовым либо добавьте своп на время сборки.
Диск. Сами образы около 2,3 ГБ, плюс данные, файлы школы и резервные копии.
Подробности и предустановочный чек-лист — на странице Установка.
Часовые пояса и локали #
Одна вещь, о которой стоит знать заранее, потому что она касается вашего сервера.
Платформа не берёт из среды ничего: ни часовой пояс, ни локаль, ни язык системы. Сервер работает в UTC, а всё, что видит человек, показывается в поясе и локали школы. Причина простая: коробка стоит на машине, про которую нам никто ничего не обещал, а «через шесть секунд» не должно превращаться в «через три часа» от того, что сервер стоит западнее.
Практически для вас это значит, что менять часовой пояс сервера не нужно и не нужно подгонять локаль. Пояс школы задаётся в её настройках.