Історія архітектури Dodo IS: шлях бекофісу

Хабр змінює світ. Понад рік ми ведемо свій блог. Десь півроку тому нам прилетів цілком логічний фідбек від хабровчан: «Додо, ось ви скрізь кажете, що у вас своя система. А що то за система? І навіщо вона потрібна сіті піцерій?».

Ми посиділи, подумали і зрозуміли, що ви маєте рацію. Ми пробуємо пояснити все на пальцях, але виходить рваними шматками і ніде немає повноцінного опису системи. Так розпочався довгий шлях збору інформації, пошуку авторів та написання серії статей про Dodo IS. Погнали!

Подяки: дякую, що ділитесь своїм фідбеком з нами. Завдяки йому ми нарешті описали систему, склали технорадар і незабаром викотимо великий опис наших процесів. Без вас так би сиділи ще 5 років.

Історія архітектури Dodo IS: шлях бекофісу

Серія статей Що таке Dodo IS? розповість про:

  1. Ранній моноліт у Dodo IS (2011-2015 роки). (In progress…)
  2. Шлях бекофісу: роздільні бази та шина. (You are here)
  3. Шлях клієнтської частини: фасад над базою (2016–2017 роки). (In progress…)
  4. Історія реальних мікросервісів. (2018–2019 роки). (In progress…)
  5. Закінчений розпил моноліту та стабілізація архітектури. (In progress…)

Якщо цікаво дізнатися ще щось — пишіть у коментарях.

Думка щодо хронологічного опису від автора
Я регулярно проводжу зустріч для нових співробітників на тему «Архітектура системи». У нас вона називається Intro to Dodo IS Architecture і є частиною процесу онбордингу нових розробників. Розповідаючи в тому чи іншому вигляді про нашу архітектуру, її особливості, у мене народився деякий історичний підхід до опису.

Традиційно ми дивимося на систему, як на набір компонентів (технічних або високорівневих), бізнесових модулів, які взаємодіють між собою задля досягнення будь-якої мети. І якщо для проектування такий погляд виправданий, то для опису та розуміння не зовсім підходить. Причин тут кілька:

  • Реальність відрізняється від того, що на папері. Не все із задуманого виходить. А нам цікаво, як насправді все виявилося та працює.
  • Послідовний виклад інформації. Насправді можна пройтися хронологічно від початку до поточного стану.
  • Від простого до складного. Чи не універсально, але в нашому випадку саме так. Від найпростіших підходів архітектура переходила до складніших. Часто через ускладнення вирішувалися проблеми швидкості реалізації та стабільності, а також десятки інших властивостей зі списку нефункціональних вимог (ось тут добре сказано про протиставлення складності іншим вимогам).

У 2011 році архітектура Dodo IS виглядала так:

Історія архітектури Dodo IS: шлях бекофісу

До 2020 року вона трохи ускладнилася і стала такою:

Історія архітектури Dodo IS: шлях бекофісу

Як відбулася ця еволюція? Навіщо потрібні різні частини системи? Які архітектурні рішення та чому були прийняті? Розберемося у цій серії статей.

Перші проблеми 2016 року: навіщо сервісам виходити з моноліту

Перші статті з циклу будуть про послуги, які першими відокремилися від моноліту. Щоб ввести вас у контекст, розповім, які проблеми були у нас у системі на початок 2016 року, що нам довелося займатися поділом сервісів.

Єдина база MySql, в яку писали свої записи всі програми, що існували на той момент у Dodo IS. Наслідки були такі:

  • Велике навантаження (при цьому 85% запитів доводилося читати).
  • База розросталася. Через це її вартість та підтримка ставали проблемою.
  • Єдина точка відмови. Якщо одна програма, що пише в базу, раптово починала робити це активніше, то інші програми відчували це на собі.
  • Неефективність у зберіганні та запитах. Часто дані зберігалися у певній структурі, яка була зручна для одних сценаріїв, але не підходила для інших. Індекси прискорювали одні операції, але могли сповільнювати інші.
  • Частина проблем зняли зроблені поспіхом кеші та read-репліки на бази (про це буде окрема стаття), але вони лише дозволили виграти час і принципово проблему не вирішували.

Проблемою була наявність самого моноліту. Наслідки були такі:

  • Єдині та рідкісні релізи.
  • Складність у спільній розробці великої кількості людей.
  • Неможливість вносити нові технології, нові фреймворки та бібліотеки.

Проблеми з базою та монолітом багато разів описувалися, наприклад, у контексті падінь на початку 2018 року (Будь як Мунк, або кілька слів про технічний обов'язок, День, коли Dodo IS зупинилася. Асинхронний сценарій и Історія про птаха Додо з роду Феніксів. Велике падіння Dodo IS), так що особливо зупинятись не буду. Скажу лише, що нам хотілося дати більшу гнучкість при розробці сервісів. Насамперед це стосувалося тих, які були найбільш навантаженими та кореневими у всій системі — Auth та Трекер.

Шлях бекофісу: роздільні бази та шина

Навігація на чолі

  1. Схема моноліту 2016 року
  2. Починаємо розвантажувати моноліт: відділення Auth та Трекера
  3. Чим займається Auth
  4. Звідки навантаження?
  5. Розвантажуємо Auth
  6. Чим займається Трекер
  7. Звідки навантаження?
  8. Розвантажуємо Трекер

Схема моноліту 2016 року

Перед вами основні блоки моноліту Dodo IS 2016, а трохи нижче розшифровка їх основних завдань.
Історія архітектури Dodo IS: шлях бекофісу
Каса доставки. Облік кур'єрів, видача замовлень кур'єрам.
Контакт центр. Прийом замовлень через оператора.
сайт. Наші сайти (dodopizza.ru, dodopizza.co.uk, dodopizza.by та ін.).
Auth. Сервіс авторизації та аутентифікації для бекофісу.
Трекер. Трекер замовлень на кухні. Сервіс позначки статусу готовності при приготуванні замовлення.
Каса Ресторану. Прийом замовлень у ресторані, інтерфейси касира.
Експорт. Вивантаження звітів у 1C для бухгалтерії.
Оповіщення та накладні. Голосові команди на кухні (наприклад, «Надійшла нова піца») + друк накладних для кур'єрів.
Менеджер Зміни. Інтерфейси роботи менеджера зміни: список замовлень, графіки продуктивності, висновок зміну співробітників.
Менеджер Офісу. Інтерфейси для роботи франчайзі та керуючого: прийом співробітників, звіти щодо роботи піцерії.
Табло Ресторану. Відображення меню на телевізорах у піцеріях.
Адмінка. Налаштування в конкретній піцерії: меню, ціни, облік, промокоди, акції, банери для сайту і т.д.
Особистий Кабінет Співробітника. Графік роботи співробітників, інформація про співробітників.
Табло Мотивації Кухні. Окремий екран, що висить на кухні та відображає швидкість роботи піццамейкерів.
Комунікація. Надсилання sms та email.
FileStorage. Власний сервіс для прийому та видачі статичних файлів.

Перші спроби вирішити проблеми допомогли нам, але стали лише тимчасовим перепочинком. Вони не стали системними рішеннями, тож було ясно, що з базами треба щось зробити. Наприклад, розділити загальну базу на більш спеціалізованих.

Починаємо розвантажувати моноліт: відділення Auth та Трекера

Основні сервіси, які тоді більше за інших записували та зчитували з бази:

  1. Auth. Сервіс авторизації та аутентифікації для бекофісу.
  2. Трекер. Трекер замовлень на кухні. Сервіс позначки статусу готовності при приготуванні замовлення.

Чим займається Auth

Auth - це сервіс, через який користувачі логіняться в бэкофіс (на клієнтській частині окремий незалежний вхід). Також до нього звертаються у запиті, щоб упевнитись, що є потрібні права на доступ, і що ці права не змінилися з останнього входу. Через нього відбувається вхід пристроїв у піцерії.

Наприклад, нам хочеться відкрити на телевізорі, що висить у залі, табло зі статусами готових замовлень. Тоді ми відкриваємо auth.dodopizza.ru, вибираємо "Вхід як пристрій", з'являється код, який можна внести в спеціальній сторінці на комп'ютері менеджера зміни, вказавши тип пристрою (девайса). ТБ сам перейде на потрібний інтерфейс своєї піцерії і почне відображати там імена клієнтів, замовлення яких готові.

Історія архітектури Dodo IS: шлях бекофісу

Звідки навантаження?

Кожен залогінений користувач бекофісу на кожен запит ходить в базу, в таблицю користувачів, через sql-запит витягує звідти користувача та перевіряє, чи має потрібні доступи і права на цю сторінку.

Кожен із пристроїв робить те саме тільки з таблицею пристроїв, перевіряючи свою роль і свої доступи. Велика кількість запитів у майстер-базу призводить до її завантаження та витрати ресурсів загальної бази на ці операції.

Розвантажуємо Auth

У Auth ізольований домен, тобто дані про користувачів, логіни або пристрої надходять у сервіс (поки майбутній) і там залишаються. Якщо вони комусь знадобляться, то він піде до цього сервісу за даними.

БУЛО. Схема роботи спочатку була такою:

Історія архітектури Dodo IS: шлях бекофісу

Хочеться трохи пояснити, як це працювало:

  1. Запит ззовні приходить на бекенд (там Asp.Net MVC), приносить із собою куку сесії, яка використовується для отримання сесійних даних із Redis(1). У ній є інформація про доступи, і тоді доступ в контролер відкритий (3,4), або ні.
  2. Якщо доступу немає, необхідно пройти процедуру авторизації. Тут спрощення вона показано як частину шляху у тому атрибуті, хоча це перехід на сторінку логіна. У разі позитивного сценарію ми отримаємо правильно заповнену сесію та перейдемо до Backoffice Controller.
  3. Якщо дані є, потрібно перевірити їх на актуальність у базі користувача. Чи не змінилася його роль, чи не треба його не пускати тепер на сторінку. У цьому випадку після отримання сесії (1) треба безпосередньо сходити до бази та перевірити доступи користувача за допомогою шару логіки аутентифікації (2). Далі або на логін-сторінку, або перехід у контролер. Така проста система, але при цьому не зовсім стандартна.
  4. Якщо всі процедури пройдені, то пропускаємо далі у логіці у контролерах та методах.

Дані користувачів відокремлені від інших даних, вони зберігаються в окремій таблиці membership, функції з шару логіки AuthService цілком можуть стати api-методами. Кордони домену визначені цілком чітко: користувачі, їх ролі, дані про доступи, видача та відкликання доступів. Все виглядає так, що можна винести на окремий сервіс.

СТАЛО. Так і зробили:

Історія архітектури Dodo IS: шлях бекофісу

Такий підхід має низку проблем. Наприклад, виклик методу всередині процесу — не те саме, що виклик по http зовнішнього сервісу. Латенсі, надійність, підтримуваність, прозорість операції зовсім інші. Докладніше саме про такі проблеми розповідав Андрій Моревський у своїй доповіді "50 відтінків мікросервісів".

Сервіс аутентифікації та з ним сервіс пристроїв використовуються для бекофісу, тобто для сервісів та інтерфейсів, що використовуються на виробництві. Аутентифікація для клієнтських сервісів (на зразок сайту або мобільного додатка) відбувається окремо без використання Auth. Відділення зайняло близько року, а зараз ми знову займаємося цією темою, перекладаючи систему вже на нові сервіси автентифікації (зі стандартними протоколами).

Чому відділення тривало так довго?
На шляху було багато проблем, які уповільнювали:

  1. Нам хотілося перевести дані про користувачів, пристрої та аутентифікацію з баз по країні в одну. Для цього довелося перекладати всі таблиці та використання з ідентифікатора int на глобальний ідентифікатор UUId (нещодавно переробляли цей код Роман Букін «Uuid – велика історія маленької структури» та open-source проект Примітиви). Зберігання даних користувачам (оскільки це персональна інформація) має обмеження і деяких країн треба зберігати їх окремо. Але глобальний ідентифікатор користувача має бути.
  2. Багато таблиць в базі має аудит інформацію про користувача, який здійснив операцію. Це вимагає додаткового механізму, щоб була консистентність.
  3. Після створення api-сервісів був довгий та поступовий період переведення на іншу систему. Перемикання мали відбуватися безшовно для користувачів і вимагали ручної роботи.

Схема реєстрації пристрою у піцерії:

Історія архітектури Dodo IS: шлях бекофісу

Загальна архітектура після виділення Auth та Devices-сервісу:

Історія архітектури Dodo IS: шлях бекофісу

Примітка. На 2020 рік ми працюємо над новою версією Auth, яка базується на стандарті авторизації OAuth 2.0. Цей стандарт досить складний, але стане в нагоді для розробки сервісу наскрізної автентифікації. У статті "Тонкості авторизації: огляд технології OAuth 2.0Ми Олексій Черняєв постарався розповісти про стандарт максимально просто і зрозуміло, щоб ви заощадили час на його вивчення.

Чим займається Трекер

Тепер про другий із навантажених сервісів. Трекер виконує двоїсту роль:

  • З одного боку, його завдання показувати співробітникам на кухні, які замовлення зараз у роботі, які продукти зараз потрібно готувати.
  • З іншого боку – оцифровувати всі процеси на кухні.

Історія архітектури Dodo IS: шлях бекофісу

Коли на замовлення з'являється новий продукт (наприклад, піца), він потрапляє на станцію трекера «Розкачування». На цій станції стоїть піцамейкер, який бере плюшку потрібного розміру і розкочує її, після чого зазначає на планшеті трекера, що виконав своє завдання і передає основу тесту на наступну станцію — «Начинення».

Там наступний піцамейкер начиняє піцу, потім зазначає на планшеті, що виконав своє завдання і ставить піцу в піч (це теж окрема станція, яку треба відзначити на планшеті). Така система була від початку додо і початку існування Dodo IS. Вона дозволяє повністю відстежувати та оцифровувати всі операції. Крім того, трекер підказує, як готувати той чи інший продукт, проводить кожен вид продукту за своїми схемами виготовлення, зберігає оптимальний час приготування продукту і трекає всі операції над продуктом.

Історія архітектури Dodo IS: шлях бекофісуТак виглядає екран планшета на станції трекера «Розкачування»

Звідки навантаження?

У кожній із піцерій приблизно по п'ять планшетів із трекером. У 2016 році ми мали більше 100 піцерій (а зараз понад 600). Кожен із планшетів робить раз на 10 секунд запит на бекенд і вигрібає дані з таблиці замовлення (зв'язка з клієнтом та адресою), складу замовлення (зв'язка з продуктом та вказівку кількості), таблиці обліку мотивації (у ній трекается час натискання). Коли піцамейкер натискає продукт на трекері, відбувається оновлення записів у всіх цих таблицях. Таблиця замовлення загальна, в неї одночасно йдуть вставки при прийнятті замовлення, оновлення від інших частин системи і численні зчитування, наприклад, на телевізорі, який висить в піцерії і показує готові замовлення клієнтам.

У період боротьби з навантаженнями, коли все і вся кешувалося і перекладалося на асинхронну репліку бази, ці операції з трекером продовжили ходити в майстер-базу. Тут не повинно бути жодного відставання, дані мають бути актуальними, розсинхронність неприпустима.

Також відсутність власних таблиць та індексів на них не дозволяло написати більш специфічні запити, заточені під своє використання. Наприклад, трекеру може бути ефективно мати індекс на піцерію на таблиці замовлень. Ми завжди вигрібаємо з бази трекера замовлення з піцерії. При цьому для прийому замовлення не так важливо, в яку піцерію він падає, важливіше, який клієнт зробив це замовлення. Отже там потрібен індекс по клієнту. Ще для трекера в таблиці замовлення не обов'язково зберігати ID надрукованого чека або пов'язані з замовленням бонусні акції. Ця інформація нашого сервісу трекера не цікавить. У загальній монолітній базі таблиці були лише компромісним варіантом між усіма користувачами. Це була одна з початкових проблем.

БУЛО. Спочатку архітектура була така:

Історія архітектури Dodo IS: шлях бекофісу

Навіть після виділення в окремі процеси більшість кодової бази залишалася спільною для різних сервісів. Все, що нижче за контролерів, було єдиним і жило в одному репозиторії. Використовувалися загальні методи сервісів, репозиторіїв, загальна база, де лежали загальні таблиці.

Розвантажуємо Трекер

Головна проблема з трекером у тому, що дані мають синхронізуватись між різними базами. Це і головна його відмінність від поділу Auth-сервісу, замовлення та його статус можуть змінюватися і мають відображатися у різних сервісах.

Ми приймаємо замовлення на Касі Ресторану (це сервіс), воно зберігається в базі статусу «Прийнятий». Після цього він має потрапити на трекер, де ще кілька разів змінить свій статус: від Кухня до Упакований. При цьому із замовленням можуть відбуватися якісь зовнішні впливи від Каси або інтерфейсу Менеджера зміни. Наведу в таблиці статуси замовлення з їх описом:

Історія архітектури Dodo IS: шлях бекофісу
Схема зміни статусів замовлення виглядає так:

Історія архітектури Dodo IS: шлях бекофісу

Статуси змінюються між різними системами. І тут трекер не є кінцевою системою, в якій замикаються дані. Ми бачили кілька можливих підходів для поділу у такому разі:

  1. Концентруємо всі дії замовлення на одному сервісі. У нашому випадку цей варіант вимагає надто великого сервісу роботи з замовленням. Якби ми зупинилися на ньому, то вийшов би другий моноліт. Проблеми ми не вирішили б.
  2. Одна система здійснює виклик в іншу. Другий варіант уже цікавіший. Але при ньому можливі ланцюжки викликів (каскадні збої), складність компонентів вище, керувати цим складніше.
  3. Організуємо події, і кожен сервіс обмінюється з іншим через ці події. У результаті було обрано саме третій варіант, яким всі послуги починають обмінюватися подіями друг з одним.

Те, що ми вибрали третій варіант, означало, що для трекера буде своя база, а на кожну зміну замовлення він посилатиме подію про це, на яку підписуються інші сервіси і яка потрапляє в майстер-базу. Для цього нам був потрібен певний сервіс, який забезпечить доставку повідомлень між сервісами.

На той час у нас у стеку вже був RabbitMQ, звідси і підсумкове рішення використати його як брокер повідомлень. На схемі показаний перехід замовлення від Каси Ресторана через Трекер, де він змінює свої статуси та відображення його на інтерфейсі Замовлення менеджера. СТАЛО:

Історія архітектури Dodo IS: шлях бекофісу

Шлях замовлення кроками
Шлях замовлення починається на одному із сервісів джерел замовлення. Тут це Каса Ресторана:

  1. На Касі повністю готове замовлення, і його час відправити на трекер. Впадає подія, на яку підписано трекер.
  2. Трекер, приймаючи собі замовлення, зберігає його у власну базу, роблячи у своїй подію «Замовлення Прийнятий Трекером» і посилаючи їх у RMQ.
  3. У шині подій на замовлення вже підписано кількох обробників. Для нас важливим є той, який робить синхронізацію з монолітною базою.
  4. Оброблювач приймає подію, вибирає з нього значущі йому дані: у разі це статус замовлення «Прийнятий Трекером» і оновлює свою сутність замовлення у основній базі.

Якщо комусь потрібне замовлення саме з монолітної таблиці orders, можна вважати його і звідти. Наприклад, таке потрібно інтерфейсу Замовлення у Менеджері Зміни:

Історія архітектури Dodo IS: шлях бекофісу

Всі інші сервіси також можуть передплатити події замовлення з трекера, щоб використовувати їх для себе.

Якщо через деякий час замовлення береться в роботу, його статус спочатку змінюється у своїй базі (базі Трекера), а потім відразу генерується подія «Замовлення». Воно також потрапляє до RMQ, звідки синхронізується в монолітній базі та доставляється іншим сервісам. На цьому шляху можуть бути різні проблеми, детальніше про них можна переглянути у доповіді Жені Пєшкова про деталі реалізації Eventual Consistency у Трекері.

Підсумкова архітектура після змін в Auth та Трекері

Історія архітектури Dodo IS: шлях бекофісу

Підсумовуючи проміжний результат: спочатку у мене була думка запакувати дев'ятирічну історію системи Dodo IS в одну статтю. Хотілося швидко та просто розповісти про етапи еволюції. Однак, сівши за матеріал, я зрозумів, що все набагато складніше і цікавіше, ніж здається.

Розмірковуючи над користю (або її відсутністю) від такого матеріалу, я дійшов висновку, що безперервний розвиток неможливий без повноцінних літописів подій, розгорнутих ретроспектив та аналізу своїх минулих рішень.

Сподіваюся, що вам було корисно та цікаво дізнатися про наш шлях. Зараз я стою перед вибором, яку частину системи Dodo IS описати у статті: пишіть у коментарях чи голосуйте.

Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.

Про яку частину Dodo IS вам хотілося б дізнатися в наступній статті?

  • 24,1%Ранній моноліт у Dodo IS (2011-2015 роки)14

  • 24,1%Перші проблеми та їх вирішення (2015-2016 роки)14

  • 20,7%Шлях клієнтської частини: фасад над базою (2016-2017 роки)12

  • 36,2%Історія справжніх мікросервісів (2018-2019 роки)21

  • 44,8%Закінчений розпил моноліту та стабілізація архітектури26

  • 29,3%Про подальші плани розвитку системи17

  • 19,0%Не хочу нічого знати про Dodo IS11

Проголосували 58 користувачів. Утрималися 6 користувачів.

Джерело: habr.com

Додати коментар або відгук