Дані були. Шляху до них — не було.
Автодилер несе багато даних: живий інвентар із цінами й доступністю, специфікації авто, прив'язані до VIN, оцінки trade-in, що змінюються щодня, варіанти фінансування, слоти сервісу. Ці дані були розкидані по vAuto для інвентаря, TradePending для оцінок trade-in, ChromeData для специфікацій авто й власній CRM кожного дилера. Ніщо з цього не подавалося покупцям у момент, коли вони ухвалювали рішення.
Розмова про купівлю — що вкладається в мій бюджет, скільки ви дасте за мій trade-in, чи можете обслужити його наступного тижня — відбувалася телефоном або у шоурумі, годинами чи днями пізніше. Дилери втрачали лідів, які не могли отримати відповідь достатньо швидко.
Бриф: розмістити чат-інтерфейс на сайті кожного дилера, що міг би відповідати на ці питання в реальному часі, під'єднаний до систем, які вже мали відповіді.
Візуальний конструктор для дилерів, чат-віджет для покупців.
Платформа мала дві різні поверхні. Дилери використовували візуальний конструктор потоків в адмінці — редактор графа карток, де вони будували розмовні потоки, з'єднуючи картки питань, гілки відповідей і картки дій: показати інвентар, отримати оцінку trade-in, забронювати запис на сервіс, захопити ліда. Код не потрібен. Кожен дилер компонував власного бота; платформа обробляла рендеринг і маршрутизацію.
Покупці бачили вбудовуваний чат-віджет на сайті дилера. Віджет запускав налаштовані потоки, витягував живий інвентар із vAuto, виводив оцінки trade-in від TradePending безпосередньо в розмові, коли покупець описував свій поточний автомобіль, і захоплював контактні дані у форматі ADF для CRM дилера. Розмова формувалася навколо того, що налаштував дилер і про що питав покупець.
Платформа була багатоорендною із першого дня. Кожен дилер отримував власну конфігурацію бота, власний граф потоків, власне під'єднання інвентаря й CRM. Один акаунт міг керувати кількома дилерськими центрами. Автентифікація, білінг і оренда жили в MySQL; усе інше жило у сховищах, що їм пасували.
- F · 01Візуальний конструктор потоків
- Дилери будували розмовні потоки в редакторі графа карток — питання, гілки відповідей, пошук по інвентарю, оцінки trade-in, захоплення лідів. Код не потрібен. Кожен дилер налаштовував власного бота.
- F · 02Фід інвентаря vAuto
- Завдання черги на основі Redis парсило FTP-експорти vAuto, оновлювало дані товарів у MongoDB й переіндексовувало Elasticsearch для кожного дилера. Зміни фіду були ізольовані в одному завданні.
- F · 03Інтеграція trade-in із TradePending
- Чат-віджет отримував оцінки trade-in від TradePending безпосередньо, виводячи оцінку в розмові, коли покупець описував свій поточний автомобіль.
- F · 04Експорт лідів у ADF
- Контактні дані покупця, захоплені в чаті, форматувалися як ADF XML і маршрутизувалися до CRM дилера. Галузевий стандартний формат означав відсутність кастомної інтеграції для кожного дилерського центру.
- F · 05Бекенд на трьох базах даних
- MongoDB для еволюційних схем ботів та інвентаря, MySQL для багатоорендної автентифікації й білінгу, Elasticsearch для пошуку по інвентарю для кожного дилера. Кожне сховище володіло одним завданням.
- F · 06Набір тестів браузерної автоматизації
- Laravel Dusk покривав повні розмовні потоки наскрізно. Зміни типів карток і схем у MongoDB не ламали шляхи віджета; набір тестів ловив це, якщо ламали.
Три бази даних, кожна заслуговує своє місце.
Рішення вести три сховища паралельно було свідомим і аргументованим із першого дня. Спокуса на проєкті на кшталт цього — нормалізувати все в одне сховище й змиритися з тертям на краях. Ми так не зробили.
MongoDB зберігав конфігурації ботів і розмовні потоки — типи карток, логіку розгалуження, варіанти швидких відповідей, схему відображення інвентаря, що еволюціонувала. Ця схема змінювалася щоспринту, у міру зростання набору функцій. Реляційна міграція на кожне додавання типу картки задушила б постачання. MongoDB поглинав ці зміни без церемоній.
MySQL обробляв усе, що потребувало транзакційної цілісності: автентифікацію, білінг і межі оренди дилерів. Ми не збиралися підпускати кінцеву узгодженість і близько до контролю доступу чи стану підписки. Elasticsearch індексував кожен активний товар інвентаря для кожного дилера, збагачений даними фіду vAuto та пропозиціями й інтерактивами, які налаштував кожен дилер. Коли покупець питав про конкретне авто в чаті, віджет звертався до Elasticsearch — а не до реляційної таблиці. Якість повноти відповіді відповідала тому, що потребував сценарій використання.
Три бази даних — це не складність, це точність. Одне сховище, що намагається виконувати всі три завдання, було б складним вибором.
Інтеграція з vAuto працювала як виділений воркер черги. vAuto публікував файли інвентаря через FTP; завдання черги Laravel на основі Redis підхоплювало їх, парсило фід, оновлювало MongoDB новими даними товарів і переіндексовувало відповідний інвентар у Elasticsearch. Кожен крок був ізольований. Коли vAuto змінював формат файлу, змінювалося одне завдання, а не весь стек. Набір тестів браузерної автоматизації в Laravel Dusk не давав повним розмовним потокам регресувати, у міру того як еволюціонували типи карток і схеми. 1 736 комітів приблизно за два роки. Архітектура трималася.
Два роки у продакшні; нуль переписувань.
Платформа вийшла для перших дилерів наприкінці 2017 року. Багатобазовий дизайн — який на архітектурній діаграмі виглядав як складність — довів свою цінність у роботі. Збої були локалізовані у своєму шарі. Збій парсингу фіду vAuto не впливав на білінг. Переіндексація Elasticsearch не блокувала розмову з покупцем. Кожне сховище відмовляло так, як відмовляє його тип, і не більше.
Прийняття дилерами трималося. Коли віджет ставить питання замість того, щоб показувати контактну форму, покупці відповідають. Потік trade-in — питання покупцю про його поточне авто, отримання оцінки TradePending, відображення її безпосередньо — працював, не вимагаючи жодного персоналу дилера.
Проєкт завершився з 708 вихідними файлами, повним набором покриття PHPUnit і Dusk і шаром даних, який власна команда клієнта розширила після передачі. Нові типи карток, нові інтеграційні конектори — вони додавали їх без нас. Три бази даних виконують три завдання, і жодна з них не виконує чужого.
