История на архитектурата Dodo IS: ранния монолит

Или всяка нещастна компания с монолит е нещастна по свой начин.

Разработката на системата Dodo IS започна веднага, след като и бизнесът Додо Пица – през 2011 година. Основата беше идеята за пълна и тотална цифровизация на бизнес процесите, а именно собствените сили, което още тогава през 2011 година предизвикваше много въпроси и скептицизъм. Но вече 9 години вървим по този път – със собствена разработка, която започна с монолит.

Тази статия е „отговор“ на въпросите „Защо да пренапишем архитектурата и да направим такива мащабни и дълги промени?“ към предишната статия „История на архитектурата Dodo IS: пътят на бекофиса“. Ще започна от това как започна разработката на Dodo IS, как изглеждаше първоначалната архитектура, как се появяваха нови модули и поради какви проблеми се наложи да направим мащабни промени.

История на архитектурата Dodo IS: ранния монолит

Серията статии „Какво е Dodo IS?“ ще разкаже за:

  1. Ранен монолит в Dodo IS (2011-2015 година). (Тук сте)

  2. Пътят на бекофиса: отделни бази и шина.

  3. Пътят на клиентската част: фасад над базата (2016-2017 година). (В процес…)

  4. История на истинските микросервизи. (2018-2019 година). (В процес…)

  5. Завършено разделяне на монолита и стабилизация на архитектурата. (В процес…)

Първоначална архитектура

През 2011 година архитектурата на Dodo IS изглеждаше така:

История на архитектурата Dodo IS: ранния монолит

Първият модул в архитектурата е приемането на поръчки. Бизнес процесът беше такъв:

  • клиентът звъни в пицарията;

  • мениджърът вдига слушалката;

  • приема поръчката по телефона;

  • паралелно я въвежда в интерфейса за прием на поръчки: взема се предвид информацията за клиента, детайлите на поръчката, адресът на доставка. 

Интерфейсът на информационната система изглеждаше приблизително така...

Първата версия от октомври 2011:

Малко подобрена през януари 2012

Информационната система Додо Пица Delivery Pizza Restaurant

Ресурсите за разработка на първия модул за приемане на поръчки бяха ограничени. Трябваше да се свърши много, бързо и с малък екип. Малкият екип – това са 2 разработчици, които положиха основите на цялата бъдеща система.

Тяхното първо решение определи по-нататъшната съдба на технологичния стек:

  • Backend на ASP.NET MVC, език C#. Разработчиците бяха .NET специалисти, този стек им беше познат и приятен.

  • Frontend на Bootstrap и jQuery: интерфейси на потребителя с авторски стилове и скриптове. 

  • База данни MySQL: без разходи за лицензи, лесна за използване.

  • Сървъри на Windows Server, защото .NET тогава можеше да бъде само под Windows (Mono няма да обсъждаме).

Физически това се изразяваше в „дедик у хостера“. 

Архитектура на приложението за приемане на поръчки

Тогава вече всички говореха за микросервизи, а SOA се използваше от около 5 години в големи проекти, например WCF излезе през 2006 година. Но тогава избраха надеждно и изпитано решение.

Ето го.

История на архитектурата Dodo IS: ранния монолит

Asp.Net MVC — това е Razor, който връща по заявка от формата или от клиента HTML-страница с рендериране на сървъра. На клиента CSS и JS скриптове показват информацията и, ако е необходимо, изпълняват AJAX заявки чрез JQuery.

Заявките на сървъра попадаха в класове *Controller, където в метода се извършва обработката и генерирането на крайна HTML-страница. Контролерите правят заявки към слоя логика, наречен *Services. Всеки от услугите отговаряше на определен аспект на бизнеса:

  • Например, DepartmentStructureService предоставяше информация за пицарии и департаменти. Департаментът е група пицарии под управлението на един франчайз.

  • ReceivingOrdersService приемаше и изчисляваше съставката на поръчката.

  • А SmsService изпращаше SMS, извиквайки API услуги за изпращане на SMS.

Услугите обработваха данни от базата, съхраняваха бизнес логиката. Във всяка услуга имаше един или няколко *Repository с съответното име. В тях вече имаше заявки към съхранявани процедури в базата и слой мапери. В съхранявани процедури имаше бизнес логика, особено много в тези, които предоставяха отчетни данни. ORM не се използваше, всички разчитаха на ръчно написан SQL. 

Още имаше слой на домейн модела и общи класове помагала, например, клас Order, който съхраняваше поръчката. Там също, в слоя, имаше помощник за преобразуване на текста на показване в зависимост от избраната валута.

Всичко това може да бъде представено с такава модел:

История на архитектурата Dodo IS: ранния монолит

Път на поръчката

Нека разгледаме опростен първоначален път за създаване на такава поръчка.

История на архитектурата Dodo IS: ранния монолит

Първоначално сайтът беше статичен. На него имаше цени, а горе — телефонен номер и надпис „Искаш пица — обади се на номера и поръчай“. За поръчка ни е необходима да реализираме прост поток: 

  • Клиентът влиза на статичния сайт с цените, избира продукти и звъни на номера, който е посочен на сайта.

  • Клиентът назовава продуктите, които иска да добави в поръчката.

  • Назовава адреса и името си.

  • Операторът приема поръчката.

  • Поръчката се показва в интерфейса на приетите поръчки.

Всичко започва с показването на менюто. Влезлият в системата оператор в един момент може да приеме само една поръчка. Затова черновата количка може да се съхранява в неговата сесия (сесията на потребителя се запазва в паметта). Там е обектът Cart, в който са продуктите и информацията за клиента.

Клиентът посочва продукта, операторът натиска на + до продукта и към сървъра се изпраща заявка. Информацията за продукта се извлича от базата и се добавя информация за продукта в количката.

История на архитектурата Dodo IS: ранния монолит

Забележка. Да, тук може да не се извлича продуктът от базата, а да се предава от фронтенда. Но за наглядност показах именно пътя от базата. 

След това въвеждаме адреса и името на клиента. 

История на архитектурата Dodo IS: ранния монолит

При натискане на „Създай поръчка“:

  • Заявката я изпращаме в OrderController.SaveOrder().

  • Получаваме Cart от сесията, там са продуктите в нужното количество.

  • Допълваме Cart с информация за клиента и предаваме на метода AddOrder на класа ReceivingOrderService, където тя се записва в базата. 

  • В базата има таблици с поръчки, състав на поръчките и клиенти, и те всички са свързани.

  • Интерфейсът за показване на поръчките излиза и извлича последните поръчки и ги показва.

Нови модули

Приемането на поръчката беше важно и необходимо. Не може да се направи бизнес с продажба на пици, ако няма приемане на поръчки. Затова системата започна да се обогатява с функционалности — приблизително от 2012 до 2015 година. През това време се появиха много различни модули на системата, които ще наричам модули, в контекста на понятието услуга или продукт. 

Модулът е набор от функции, които са обединени под някаква обща бизнес цел. При това физически те се намират в едно приложение.

Модулите могат да се нарекат блокове на системата. Например, това е модул за отчети, интерфейси на админ панела, тракер на продуктите в кухнята, авторизация. Това са различни интерфейси за потребителя, някои дори имат различни визуални стилове. При това всички в рамките на едно приложение, един работещ процес. 

Технически модулите бяха оформени като Area (такава идея дори остана в asp.net core). Имаше отделни файлове за фронтенда, моделите, както и свои класове контролери. В крайна сметка системата се трансформира от такава…

История на архитектурата Dodo IS: ранния монолит

…в такава:

История на архитектурата Dodo IS: ранния монолит

Някои модули са реализирани като отделни сайтове (executable project), поради съвсем отделни функционалности и частично заради по-отделно, по-фокусирано разработване. Това е:

  • Site — първата версия на сайта dodopizza.ru.

  • Експорт: извличане на отчети от Dodo IS за 1C. 

  • Личен — личен кабинет на служителя. Разработван отделно, с отделна точка за достъп и отделен дизайн.

  • fs — проект за хостинг на статичен контент. По-късно преминахме на CDN Akamai за цялата статия. 

Останалите блокове бяха в приложението BackOffice. 

История на архитектурата Dodo IS: ранния монолит

Обяснение на имената:

  • Касата — Касата на ресторанта.

  • ShiftManager — интерфейси за ролята "Мениджър на смяна": оперативна статистика за продажбите на пицарията, възможност за добавяне на продукти в стоп-лист, промяна на поръчки.

  • OfficeManager — интерфейси за ролята "Управляващ пицария" и "Франчайзи". Тук са събрани функции за настройка на пицарията, бонусни акции, прием и работа с служители, отчети.

  • PublicScreens — интерфейси за телевизори и таблети, окачени в пицариите. На телевизорите се показва меню, рекламна информация, статус на поръчката при предаване. 

Използваха общ слой от услуги, общ блок от домейнни класове Dodo.Core, както и обща база. Понякога можеха да преминават помежду си. Включително и отделни сайтове, като dodopizza.ru или personal.dodopizza.ru, също се свързваха с общите услуги.

При появата на нови модули, се стараехме да преизползваме максимално вече създадения код на услуги, процедури и таблици в базата. 

За по-добро разбиране на мащаба на модулите, направени в системата, ето схема от 2012 година с плановете за развитие:

История на архитектурата Dodo IS: ранния монолит

До 2015 година всичко на схемата и дори повече беше в продукция.

  • Приемането на поръчки израсна в отделен блок на Контактния Център, където поръчката се приема от оператор.

  • Появиха се общодостъпни екрани с меню и информация, окачени в пицариите.

  • В кухнята има модул, който автоматично възпроизвежда гласово съобщение "Нова пица" при получаване на нова поръчка, а също така отпечатва товарителница за куриера. Това значително опростява процесите в кухнята и позволява на служителите да се съсредоточат върху по-сложни операции.

  • Блокът за доставка се превърна в отделна Каса за Доставка, където поръчката се предава на куриера, който предварително е влязъл в смяна. Работното му време се отчита за изчисляване на заплатата. 

Паралелно с 2012 до 2015 г. се появиха над 10 разработчици, откриха се 35 пицарии, внедриха система в Румъния и се подготвиха за откриване на точки в САЩ. Разработчиците вече не се занимаваха със всички задачи, а бяха разделени на екипи, всеки от които се специализираше в своята част от системата. 

Проблеми

Включително заради архитектурата (но не само).

Хаос в базата

Една база — удобно. В нея може да се постигне последователност, и то благодарение на средствата, вградени в релационните бази. Работата с нея е позната и удобна, особено, ако там има малко таблици и малко данни.

Но за 4 години разработка в базата се оказаха около 600 таблици, 1500 съ хранимите процедури, в много от които имаше и логика. Уви, хранимите процедури не предоставят особено предимство при работа с MySQL. Те не се кешират от базата, а съхраняването на логиката в тях усложнява разработката и отстраняването на грешки. И повторното използване на кода също е затруднено.

На много таблици не бяха налични подходящи индекси, където, обратно, имаше твърде много индекси, което затрудняваше вмъкването. Беше необходимо да се модифицират около 20 таблици — транзакцията за създаване на поръчка можеше да отнеме около 3-5 секунди. 

Данните в таблиците не винаги бяха в най-подходящата форма. Някои места се нуждаеха от денормализация. Част от редовно получените данни се съхраняваха в колоната под формата на XML структура, което увеличаваше времето за изпълнение, удължаваше запитванията и усложняваше разработката.

Към едни и същи таблици се правеха много разнородни запитвания. Особено страдаха популярните таблици, като споменатата таблица orders или таблицата pizzeria. Те се използваха за извеждане на оперативни интерфейси в кухнята и аналитични данни. Към тях се обръщаше и сайтът (dodopizza.ru), където по всяко време можеше внезапно да дойдат много запитвания. 

Данните не бяха агрегирани и много изчисления се извършваха на летящо посредством базата. Това създаваше излишни изчисления и допълнителна натовареност. 

Често кодът влизаше в базата, когато не трябваше. Някъде липсваха bulk операции, а другаде трябваше да се раздели едно запитване на няколко чрез кода, за да се ускори и увеличи надеждността. 

Свързаността и заплитането в кода

Модулите, които трябваше да отговарят за своя участък от бизнеса, не го правеха честно.. Някои от тях имаха дублиране на функции за ролите. Например, местният маркетолог, който отговаряше за маркетинговата активност на мрежата в своя град, трябваше да използва както интерфейса на „Администратора“ (за създаване на промоции), така и интерфейса на „Офис Мениджъра“ (за преглед на влиянието на промоциите върху бизнеса). Разбира се, вътре и двата модула използваха един и същ сервиз, който работеше с бонус промоции.

Сервизите (класовете в рамките на един монолитен голям проект) можеха да извикват един друг за обогатяване на данните си.

С класовете модели, които съ хранят данните, работата в кода беше разнообразна. Някои места имаха конструктори, чрез които можеше да се зададат задължителни полета. На други места това ставаше чрез публични свойства. Разбира се, получаването и преобразуването на данни от базата беше разнообразно. 

Логиката беше или в контролерите, или в класовете на услугите. 

Това изглежда като незначителни проблеми, но те значително забавяха разработката и намаляваха качеството, което водеше до нестабилност и грешки. 

Сложността на голямата разработка

Трудностите се появиха и в самата разработка. Трябваше да се правят различни блокове от системата, при това паралелно. Уместяването на нуждите на всеки компонент в един код ставаше все по-трудно. Не беше лесно да се договориш и да угодиш на всички компоненти едновременно. Към това се добавяха ограничения в технологиите, особено що се отнася до базата и фронтенда. Трябваше да се откажем от JQuery в полза на високоуровневи фреймворкове, особено в частта на клиентските услуги (сайт).

В някои части на системата можеха да се използват бази, по-подходящи за това. Например, по-късно имахме прецедент с преход от Redis на CosmosDB за съхранение на количката за поръчки. 

Отборите и разработчиците, занимаващи се с техните области, явно искаха по-голяма самостоятелност за своите услуги, както в частта на разработването, така и при пускането. Конфликти при свързването, проблеми при релизите. Ако за 5 разработчици този проблем е незначителен, то при 10, а още повече при планирано нарастване, всичко би станало по-сериозно. А напред трябваше да се разработи мобилно приложение (то стартира през 2017 г., а през 2018 г. имаше голям спад). 

Различни части на системата изискваха различни показатели за стабилност., но поради силната свързаност на системата, не можахме да го осигурим. Грешка при разработването на нова функция в администраторския панел лесно би могла да застраши приемането на поръчки на сайта, тъй като кодът е общ и многоизползваем, базата данни и данните също са единни.

Вероятно е било възможно дори в рамките на такава монолитно-модулна архитектура да не допуснем тези грешки и проблеми: да се направи разделение на отговорностите, да се провежда рефакторинг както на кода, така и на базата данни, ясно да се отделят слоевете един от друг, да се следи качеството всеки ден. Но избраните архитектурни решения и фокусът върху бързото разширяване на функционалността на системата доведоха до проблеми със стабилността.

Как блогът Сила на ума постави касите в ресторантите

Ако растежът на мрежата от пицарии (и натоварването) продължаваше в същия темп, след известно време падението щеше да е толкова голямо, че системата да не може да се възстанови. Добре илюстрира проблемите, с които започнахме да се сблъскваме около 2015 година, следващата история. 

В блога „Сила на ума“ имаше виджет, който показваше данни за приходите за годината от цялата мрежа. Виджетът се обръщаше към публичното API на Dodo, което предоставя тези данни. Сега тази статистика е налична на http://dodopizzastory.com/. Виджетът се показваше на всяка страница и правеше заявки по таймер на всеки 20 секунди. Заявката отиваше в api.dodopizza.ru и запитваше:

  • броя на пицариите в мрежата;

  • общата приход на мрежата от началото на годината;

  • приходът за днес.

Запитването за статистиката по приходите отиваше директно в базата данни и започваше да запитва данните по поръчките, агрегираше данните на момента и извеждаше сумата. 

В тази съща таблица на поръчките идваха касите в ресторантите, извеждаха списъка с приетите днешни поръчки, новите поръчки също се добавяха в нея. Касите правеха своите заявки на всеки 5 секунди или при обновяване на страницата.

Схемата изглеждаше така:

История на архитектурата Dodo IS: ранния монолит

Един есенен ден, Фьодор Овчинников написа дълга и популярна статия в своя блог. В блога дойдоха много хора и започнаха внимателно да четат всичко. Докато всеки от дошлите четеше статията, виджетът с приходите работеше безотказно и правеше заявки към API на всеки 20 секунди.

API извикаше съ хранилище процедура за изчисление сумата на всичките поръчки от началото на годината за всички пицарии в мрежата. Агрегацията беше извършвана по таблицата orders, която е много популярна. В нея влизат всички каси на всички отворени ресторанти по това време. Кассите спряха да отговарят, поръчките не се приемаха. Също така те не се приемаха от сайта, не се появяваха в тракера, мениджърът на смяната не можеше да ги види в своя интерфейс. 

Това не е единствената история. Към есента на 2015 година всяка петък натоварването на системата беше критично. Няколко пъти изключвахме публичното API, а веднъж дори трябваше да изключим сайта, тъй като нищо не помагаше. Имаше дори списък на услугите с реда на изключване при сериозни натоварвания.

От този момент започва нашата борба с натоварванията и за стабилизацията на системата (от есента на 2015 до есента на 2018). Именно тогава се случи „Великото падение“. След това понякога също се случваха неизправности, някои от които бяха доста чувствителни, но общият период на нестабилност сега може да се счита за преминат.

Бурен растеж на бизнеса

Защо не можеше да се „направи веднага добре“? Достатъчно е да погледнем следните графики.

История на архитектурата Dodo IS: ранния монолит

Също така през 2014-2015 г. имаше откритие в Румъния и се подготвяше откритие в САЩ.

Мрежата растеше много бързо, откриваха се нови страни, появяваха се нови формати на пицарии, например, отворихме пицария на фудкорт. Всичко това изискваше значително внимание точно към разширението на функциите на Dodo IS. Без всички тези функции, без проследяване в кухнята, отчет на продуктите и загубите в системата, показване на издаването на поръчките в залата на фудкорта, едва ли сега бихме обсъждали „правилната“ архитектура и „верния“ подход към разработката.

Още препятствия за навременен преглед на архитектурата и изобщо внимание към техническите проблеми беше кризата от 2014 година. Такива неща жестоко удрят върху възможностите за растеж на екипите, особено за млад бизнес, какъвто беше Додо Пица.

Бързи решения, които помогнаха

Проблемите изискваха решения. Условно, решенията могат да бъдат разделени на 2 групи:

  • Бързи, които гаснат пожара и дават малък запас от здравина и печелят време за промени.

  • Системни и затова дълги. Рейинженеринг на редица модули, разделяне на монолитната архитектура на отделни услуги (повечето от които всъщност не са микро, а по-скоро макроуслуги и по този въпрос има доклад Андрея Моревского). 

Сухой список быстрых изменений таков:

Scale up мастер базы

Разбираемся, как бороться с нагрузками, путем увеличения мощности сервера. Это делали как для мастер базы, так и для веб-серверов. К сожалению, это возможно лишь до определенного предела, после которого затраты становятся слишком высокими.

С 2014 года мы начали использовать Azure, и на эту тему мы писали в статье «Как Додо Пицца доставляет пиццу с помощью облака Microsoft Azure». Однако после серии увеличений серверов для базы, мы уперлись в предел стоимости. 

Реплики базы на чтение

Создано две реплики для базы:

ReadReplica для запросов на справочники. Они используются для чтения справочников, таких как города, улицы, пиццерии и продукты (slowly changed domain), и там, где допускается небольшая задержка. У нас было два таких реплики, и мы обеспечивали их доступность так же, как и для мастера.

ReadReplica для запросов на отчеты. У этой базы была более низкая доступность, но туда поступали все отчеты. Пусть у них есть тяжелые запросы на огромные перерасчеты данных, но они не влияют на основную базу и операционные интерфейсы. 

Кэши в коде

Кэшей в коде не было нигде (совсем). Это приводило к дополнительным, не всегда нужным запросам к нагруженной базе. Кэши были изначально как в памяти, так и на внешнем кэш-сервисе, который использовал Redis. Все кэши инвалидировались со временем, настройки указывались в коде.

Несколько серверов для бэкэнда

Бэкэнд приложения тоже нужно было масштабировать, чтобы выдерживать повышенные нагрузки. Необходимо было создать кластер из одного iis-сервера. Мы перенесли сессию приложений из памяти на RedisCache, что позволило создать несколько серверов, стоящих за простым балансировщиком нагрузки с round robin. Сначала использовался тот же Redis, что и для кэшей, затем мы разделили на несколько. 

В итоге архитектура усложнилась…

История на архитектурата Dodo IS: ранния монолит

…но часть напряжения удалось снять.

А дальше нужно было переработать нагруженные компоненты, за что мы и взялись. Об этом мы расскажем в следующей части.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster