01Про проєкт
Admister — мультивертикальний маркетплейс. Під одним дахом працюють дошка оголошень, авто із запчастинами, нерухомість із продажем і орендою, товарний ринок із магазинами продавців, цифрові товари, купонний сервіс і прайс-агрегатор. У кожного свій піддомен, своя головна і своє уявлення про те, що таке «оголошення».
Ззовні це сім різних продуктів. Усередині — один застосунок: сервіс визначається за адресою запиту, а все, що нижче — акаунти, гроші, модерація, пошук, медіа, сповіщення, — спільне. Проєкт наш цілком: модель, рушій, інтерфейс і запуск кожної вертикалі.
02Контекст і проблема
Звичайний спосіб запустити кілька маркетплейсів — запустити кілька сайтів. Він працює до другого: з’являються дві таблиці користувачів, дві черги модерації, дві інтеграції з оплатою і два місця, де ту саму помилку треба лагодити двічі — і по-різному, бо код на той час уже розійшовся.
Протилежна крайність не краща. Сайт із категорією «Авто» не вміє питати пробіг, двигун і номер так, як це робить авто-дошка, а пошук нерухомості без поверху й кількості кімнат — не пошук нерухомості. Завдання було в тому, щоб лишити один рушій і водночас не вдавати, ніби сім дуже різних каталогів — це один каталог.
03Завдання проєкту
- одна кодова база й один акаунт на всі вертикалі, сервіс обирається за адресою;
- власні атрибути та фільтри в кожної вертикалі: десятки полів в авто й нерухомості, і жодного з них — в інших сервісах;
- гроші, яким власник довіряє: баланс, криптоінвойси, ідемпотентне зарахування та звіряння завислих платежів;
- причини повертатися: збережені пошуки з пушами, бейджі продавців, аукціони та групові закупівлі;
- дві мови й чотири валюти показу без дублювання контенту;
- мобільний застосунок, що ходить у свій BFF, а не розбирає сторінки сайту.
04Що ми зробили
Реєстр сервісів вбудовано в завантаження застосунку. Надходить запит, адреса звіряється з реєстром — і далі застосунок знає, яка він вертикаль: які модулі піднімати, який набір атрибутів брати, яку головну та яке меню малювати. Голий домен — дошка оголошень, тож найкоротша адреса водночас і найзагальніша.
Усе, що не залежить від вертикалі, написано один раз: акаунти й сесії, баланс і платежі, приймання медіа, модерація, збережені пошуки, сповіщення, карта сайту та API. Додати сьомий сервіс означало написати його атрибути та його головну — а не ще один маркетплейс.
05Сім сервісів — сім каталогів
- Оголошення — загальна дошка: від велосипеда до дивана, публікація безкоштовна.
- Авто — транспорт, номери та запчастини, з власним набором із кількох десятків атрибутів і фільтрами поверх нього.
- Нерухомість — продаж і оренда, власний набір атрибутів, карти та пошук по районах.
- Маркет — товари з магазинами продавців: вітрина на продавця, залишки, замовлення.
- Цифрові товари — ключі, гіфт-карти та eSIM: те, що доставляють, а не відправляють.
- Купони — промокоди та акції магазинів-партнерів.
- Прайс — агрегатор цін: один товар у різних магазинах, порівняння та враховані переходи.
У них спільні акаунт, баланс, черга модерації та пошуковий індекс — і нічого спільного в атрибутах, заради чого все й робилося.
06Гроші: баланс, криптоінвойси, звіряння
Платне — просування, тарифи магазинів, цифрові товари — оплачується з внутрішнього балансу, а баланс поповнюється криптою через Payora, наш же шлюз. Тіло інвойсу збирається рівно в одній функції, бо те саме тіло повторює крон звіряння під тим самим ключем ідемпотентності: одне розбіжне поле повернулося б конфліктом і підвісило платіж.
Дві деталі, які ми повторимо на будь-якому шлюзі. В інвойс іде мова самого покупця з його рядка в базі, а не з вебзапиту: у крона запиту немає, і інакше всім ішла б англійська. І адреса повернення веде в кабінет на голому домені — покупець, що почав з авто-вертикалі, повертається залогіненим, а не на сторінку «увійдіть знову».
07Довіра: модерація, бейджі, суперечки
Маркетплейс судять за найгіршим оголошенням, тому модерація тут не бічний екран. Кожне рішення — схвалити, відхилити, зняти — проходить через один обробник, і «відхилити» навмисно не те саме, що «зняти»: перше — відповідь авторові з причиною, друге прибирає те, що вже висіло. Два дієслова, два різні листи, два різні стани в записі.
У продавців і магазинів є бейджі, які заробляють, а не купують: скільки живе акаунт, скільки угод закрито, чи підтверджені телефон і пошта. Покупець бачить той самий бейдж усюди, де зустрічає продавця, бо рахується він в одному місці.
08Пошук, збережені пошуки та пуші
Пошук знає, в якій він вертикалі. На авто-дошці він пропонує поля, які має машина; на нерухомості — кімнати, поверх і район; на загальній дошці лишається навмисно простим. Підказки надходять з окремої точки, тому поле відповідає, поки сторінку ще читають.
Пошук можна зберегти — і саме це перетворює відвідувача на такого, що повертається. Нові збіги надходять вебпушем, підписка належить акаунту, а не вкладці браузера, і тим самим механізмом ідуть події, на які справді чекають: аукціон ось-ось закриється, групова закупівля набрала поріг.
09Мобільний застосунок із власним бекендом
Застосунок не розбирає сторінки сайту. Він ходить у свій BFF: невеликий набір точок, що відповідають у тих формах, які потрібні екранам, із токенами, які видаються окремо від вебсесій. Без токена відкриті два шляхи — довідники, потрібні до входу, і адреса живості.
Адреса живості окрема навмисно. Реєстр, що стежить за флотом застосунків, раніше питав довідники, а вони збирають усі словники й курси, — перевірка живості не має коштувати дорожче за саму роботу. Він же віддає версію договору: клієнт, зібраний під стару версію, видно раніше, ніж це помітять його користувачі.
10SEO на сім фронтів
Сім сервісів на одному домені — це сім карт сайту, сім наборів структурованих даних і дуже простий спосіб наплодити дублі. Кожна вертикаль публікує свою карту з кешу, сторінки оголошень оголошують свій тип розмітки, а сторінки результатів пошуку ніколи не видають себе за канонічну адресу оголошення.
Контент теж автоматизований: потік матеріалів надходить з Astrina — платформи, яку ми зробили саме для цього, — і щойно стаття приходить, кеш карти сайту її вертикалі скидається. Інакше нова сторінка чекала б наступної перезбірки, і ніхто б не зрозумів, чому її немає.
11Один канал назовні
Усе, що застосунок надсилає назовні — виклики API, вебхуки, партнерські фіди, забирані картинки, пошта, — іде через один модуль, прив’язаний до SOCKS5-проксі, і DNS резолвиться через той самий проксі. Модуль працює fail-closed: недоступний проксі — запит блокується, а не йде тихо з адреси сервера.
Це не прикраса. Маркетплейс цілий день тягне партнерські фіди й чужі картинки, і кожен такий запит інакше відносив би адресу майданчика третій стороні. Пошта йде тим самим шляхом, через YourTrend: листи підписані, а ім’я сервера не з’являється в заголовках.
12Технології
- PHP 8.3 без фреймворка, тонка обгортка над PDO і підготовлені запити всюди;
- MariaDB, схема живе нумерованими файлами міграцій, а не в чиїйсь пам’яті;
- медіа лежать поза вебрутом і віддаються контролером — завантажений файл ніколи не стає виконуваним шляхом;
- збірка асетів із хешем в імені й без інлайну того, що має кешуватися, з вимикачем на випадок збою;
- SOCKS5 назовні для кожного вихідного виклику, пошта через SMTP YourTrend;
- Payora — криптоінвойси, Astrina — вхід і матеріали, Adgora — власна реклама.
13Результат
Сім живих сервісів на одній кодовій базі, один акаунт і один баланс на всіх, і при цьому атрибути та фільтри різняться рівно настільки, наскільки різняться каталоги. Нова вертикаль — це набір атрибутів і головна сторінка, а не новий проєкт, не друга таблиця користувачів і не друга інтеграція з оплатою.
Власникові це дає одне місце, куди дивитися. Модерація, гроші, продавці та контент ведуться із загальної панелі керування всієї групи проєктів, а маркетплейс віддає їх підписаним адмінським API, а не другим власним адмін-сайтом.
14Висновки
Висновок, який ми понесемо в наступний маркетплейс: спільним роблять механізм, а не зміст. Акаунти, гроші, модерація та пошук — те саме завдання в кожній вертикалі, і написати його треба один раз. Атрибути, фільтри й сама форма оголошення — це і є продукт, і спроба вкласти їх в одну універсальну таблицю робить маркетплейс незручним одразу для всіх.
Другий висновок — про гроші. Місце, де описується платіж, зобов’язане бути єдиною функцією: його однаково повторить хтось інший — крон, повтор, звіряння, — а два описи одного платежу це найдешевший спосіб тихо втратити гроші.
Якщо ви будуєте маркетплейс, дошку оголошень або мультивертикальний майданчик на одному рушії — це робота, яку ми робимо цілком.



