Маркетплейс, а не стриминговый сервис. Эта разница важна архитектурно.
Оператор строил не стриминговый сервис, где артисты загружают ради охвата, а доход приходит позже как доля от рекламы. Артисты владеют своим контентом. Слушатели за него платят. Платформа — это маркетплейс: листинги товаров, корзина, оформление, история заказов, отчёты по продажам. Бизнес-модель живёт в этом потоке.
Это различие меняет архитектуру. Самая сложная задача стримингового сервиса — пропускная способность приёма и задержка воспроизведения. Самая сложная задача маркетплейса — коммерческая поверхность: конкурентные операции с корзиной, согласованность заказов, оформление, которое не теряет покупку, если фоновый воркер медлит. Ошибитесь в этом — и вы теряете реальные транзакции от артистов, которые и так скептичны к новой платформе.
У оператора также был план на мобильный клиент — iOS и Android, который будет построен позже — и он хотел, чтобы API-слой был к нему готов. Не дооснащён задним числом. Готов с самого начала, с версионированными DTO-эндпоинтами и чётким разделением Admin/Mobile. Мобильное приложение не вышло в окне 2022 года. API-слой — вышел.
Бриф: полноценная коммерческая поверхность для каталога, принадлежащего артистам, асинхронное ядро, которое не блокирует оформление фоновой работой, и API-слой, который не придётся переосмысливать, когда появится мобильный клиент.
Два слоя, шина сообщений и три хранилища с разными задачами.
Система с первого дня делилась на два приложения Symfony. Монорепозиторий — админка и веб-UI — отвечал за управление профилями артистов, листинги товаров, загрузку обложек и бэк-офис оператора. API-слой был построен отдельно, как REST-сервис с версионированием через DTO и пространствами маршрутов /Mobile/v1/ и /Admin/v1/, 96 мобильных эндпоинтов и 130 админских, задокументированный и готовый к мобильному клиенту, когда бы тот ни появился.
Потоки корзины и заказов идут через Symfony Messenger, а не через синхронные записи. AddToCartController отправляет сообщение; обработчик обрабатывает мутацию корзины асинхронно. Записи заказов — Order и ProductOrder, с количеством и общей стоимостью, отслеживаемыми по каждой строке — следуют тому же паттерну. Оформление не блокируется записями телеметрии, обработкой обложек или чем-либо ещё, идущим по шине. Операционная база данных остаётся быстрой, потому что веерное распределение записей управляемо.
Три хранилища, у каждого — одна зона ответственности. PostgreSQL — каноническое состояние: 25 сущностей, более 40 индексов на горячих таблицах — пользователи, товары, заказы, позиции корзины, жанры, теги, связи «артист — товар». Redis работает и как брокер очереди Messenger, и как слой кэша для горячих чтений. InfluxDB хранит телеметрию действий пользователей. Высокочастотные поведенческие события никогда не касаются операционной базы данных.
Профили артистов и описания товаров — названия альбомов, имена треков, метки жанров, новости — переводимы на уровне схемы через KnpLabs DoctrineBehaviors. Добавление локали — это миграция данных, а не изменение кода. Веб-UI использовал Plyr для воспроизведения аудио и видео; CropperJS отвечал за обрезку и загрузку обложек прямо на странице.
- F · 01Корзина и оформление через Messenger
- Мутации корзины и обработка заказов идут через Symfony Messenger, а не синхронные записи. Оформление подтверждается мгновенно; шина берёт на себя веерное распределение записей.
- F · 02Двухслойная архитектура
- Монорепозиторий (админка и веб-UI) и отдельный API-сервис с версионированием через DTO и пространствами /Mobile/v1/ и /Admin/v1/ — 96 мобильных эндпоинтов и 130 админских — построенный до того, как появился мобильный клиент.
- F · 03Разделение на три хранилища
- PostgreSQL для канонического состояния (25 сущностей, более 40 индексов), Redis для брокера очереди и кэша, InfluxDB для телеметрии действий пользователей. У каждого хранилища одна задача.
- F · 04Переводимый контент на уровне схемы
- KnpLabs DoctrineBehaviors на описаниях товаров, профилях артистов, метках жанров и новостях. Добавление локали — это миграция, а не изменение кода.
- F · 05Конвейер телеметрии InfluxDB
- События действий пользователей идут через обработчики Messenger в InfluxDB. Конвейер спроектирован под полную телеметрию проигрывания, перемотки и пропуска, не касаясь операционной базы данных.
- F · 06Асинхронная обработка заказов
- Сущности Order и ProductOrder отслеживают покупки с количеством и стоимостью по каждой строке. Мутации заказов идут через шину сообщений, чтобы коммерческие операции не конкурировали друг с другом.
Корзина идёт через шину. API существует раньше приложения, которое будет его вызывать.
Решение направить мутации корзины через Symfony Messenger, а не через синхронные записи, было не про масштаб. Оно было про корректность. Операции с корзиной в маркетплейсе включают проверки склада, валидацию состояния товара и обработку конкурентных сессий. Вынос их на шину сообщений отвязывает подтверждение для пользователя от веерного распределения записей. Слушатель получает ответ; платформа догоняет. Операционная база данных никогда не соревнуется с коммерческой поверхностью.
Тот же принцип проходит и через воспроизведение. PlayFileHandler и PlayedFileService отвязывают события воспроизведения от операционной базы данных. Телеметрия действий пользователей пишется в InfluxDB через шину сообщений — всего 21 обработчик в API-слое. Конвейер InfluxDB спроектирован под полную телеметрию проигрывания, перемотки и пропуска; подключение этих дополнительных событий — следующий шаг, который оператор может сделать, ничего не переархитектуривая.
Три хранилища — это ответ, когда каждое хранилище заслуживает своё место, делая работу, в которой оно сильнее всего, — и ничего больше.
API-слой был намеренно избыточен для 2022 года. /Mobile/v1/ существовал с 96 эндпоинтами до того, как была написана хоть строчка кода мобильного приложения. Мобильный клиент не вышел в окне 2022 года, но оператор унаследовал API-сервис, который уже был версионирован, задокументирован и отделён от админ-поверхности. Цена сделать это позже — с живым продуктом, реальными данными пользователей и разошедшейся схемой — несопоставима с ценой сделать это один раз в начале.
Восемь месяцев. Три репозитория. Коммерческая поверхность, которую оператор может расширять.
Восемь месяцев активной разработки. 494 коммита в трёх репозиториях — монорепозиторий, API-слой и минимальный лендинг на HTML/Gulp. Система вышла с полноценной коммерческой поверхностью «артист — слушатель»: листинги товаров, корзина, оформление, история заказов и отчёты по продажам. Асинхронная архитектура — шина Messenger, брокер Redis, конвейер телеметрии InfluxDB — была в продакшене с первого публичного релиза.
Мобильный клиент был единственной запланированной поверхностью, не вышедшей в окне 2022 года. API-слой, построенный, чтобы его принять, цел. Пространство /Mobile/v1/, контракты DTO, обработка аутентификации — всё это ждёт клиента, а не наоборот.
У архитектуры, которую унаследовал оператор, три чёткие границы: Postgres для состояния, Redis для очереди и кэша, InfluxDB для временных рядов. Каждый может отказать, быть обновлён или масштабирован независимо. Следующему инженеру не нужна шестичасовая вводная. Интересная задача — асинхронная коммерческая поверхность — решена. Всё после этого — продуктовая работа.
Мы строили под мобильный клиент до того, как он появился. Когда он выйдет, платформа этого не заметит.
