Historie architektury Dodo IS: raný monolit

Nebo každá nešťastná firma s monolitem je nešťastná po svém.

Vývoj systému Dodo IS začal hned, jak začal byznys Dodo Pizzy — v roce 2011. Základem byla myšlenka úplné a totální digitalizace obchodních procesů, přičemž vlastními silami, což už tehdy v roce 2011 vyvolávalo spoustu otázek a skepticismu. Ale teď už 9 let jdeme touto cestou — s vlastním vývojem, který začínal s monolitem.

Tento článek je "odpovědí" na otázky "Proč přepracovávat architekturu a provádět tak rozsáhlé a dlouhé změny?" na předchozí článek "Historie architektury Dodo IS: cesta back office".Začnu tím, jak začínal vývoj Dodo IS, jak vypadala původní architektura, jak vznikaly nové moduly a kvůli jakým problémům bylo nutné provádět rozsáhlé změny.

Historie architektury Dodo IS: raný monolit

Série článků "Co je Dodo IS?" poví o:

  1. Přední monolit v Dodo IS (2011-2015). (Jste zde)

  2. Cesta back office: oddělené databáze a sběrnice.

  3. Cesta klientské části: fasáda nad databází (2016-2017). (Probíhá…)

  4. Historie skutečných mikroslužeb. (2018-2019). (Probíhá…)

  5. Dokončené rozdělení monolitu a stabilizace architektury. (Probíhá…)

Původní architektura

V roce 2011 vypadala architektura Dodo IS takto:

Historie architektury Dodo IS: raný monolit

První modul v architektuře — příjem objednávek. Obchodní proces byl následující:

  • zákazník volá do pizzerie;

  • telefon zvedá manažer;

  • přijímá objednávku po telefonu;

  • současně ji zadává do rozhraní pro příjem objednávek: bere se v úvahu informace o zákazníkovi, údaje o objednávce, adresa doručení. 

Rozhraní informačního systému vypadalo přibližně takto...

První verze z října 2011:

Trochu vylepšená v lednu 2012

Informační systém Dodo Pizza Delivery Pizza Restaurant

Zdroje pro vývoj prvního modulu příjmu objednávek byly omezené. Bylo třeba udělat hodně, rychle a s malým týmem. Malý tým — to jsou 2 vývojáři, kteří položili základ celé budoucí soustavy.

Jejich první rozhodnutí určilo další osud technologického stacku:

  • Backend na ASP.NET MVC, jazyk C#. Vývojáři byli .NET specialisté, tento stack jim byl známý a příjemný.

  • Frontend na Bootstrap a JQuery: uživatelská rozhraní na vlastních stylech a skriptech. 

  • Databáze MySQL: bez nákladů na licence, jednoduchá na používání.

  • Servery na Windows Server, protože .NET tehdy mohl být pouze pod Windows (Mono nebudeme diskutovat).

Fyzicky to všechno vyjadřovalo „dedikovaný server u poskytovatele hostingu“. 

Architektura aplikace pro příjem objednávek

Tehdy už všichni mluvili o mikroservisách, zatímco SOA se pět let využívalo ve velkých projektech, například WCF bylo vydáno v roce 2006. Ale tehdy se zvolilo spolehlivé a vyzkoušené řešení.

A je to tady.

Historie architektury Dodo IS: raný monolit

Asp.Net MVC je Razor, který na základě požadavku z formuláře nebo od klienta generuje HTML stránku s vykreslením na serveru. Na klientovi již CSS a JS skripty zobrazují informace a v případě potřeby provádějí AJAX požadavky pomocí JQuery.

Požadavky na serveru přicházejí do tříd *Controller, kde se v metodě provádí zpracování a generování finální HTML stránky. Kontroléry provádějí požadavky na logiku, nazývanou *Services. Každá z těchto služeb byla zodpovědná za nějaký aspekt podnikání:

  • Například DepartmentStructureService poskytoval informace o pizzeriích a odděleních. Oddělení je skupina pizzerií pod správcem jednoho franšízanta.

  • ReceivingOrdersService přijímal a počítal složení objednávky.

  • A SmsService odesílal SMS, volal API služby pro odesílání SMS.

Služby zpracovávaly data z databáze a uchovávaly obchodní logiku. V každé službě byla jedna nebo více *Repository s odpovídajícím názvem. Tam byly uloženy požadavky na uložené procedury v databázi a vrstva mapovačů. Ve uložených procedurách byla obchodní logika, zvlášť mnohem více v těch, které vracely výkazní data. ORM se nepoužívalo, všichni se spolehli na ručně napsané SQL. 

Také byla vrstva doménového modelu a společných pomocných tříd, například třída Order, která uchovávala objednávku. Tam byl také pomocník pro převod textu zobrazení podle vybrané měny.

To vše lze představit takto:

Historie architektury Dodo IS: raný monolit

Cesta objednávky

Zvažme zjednodušenou původní cestu k vytvoření takové objednávky.

Historie architektury Dodo IS: raný monolit

Původně byla stránka statická. Měla ceny a nahoře bylo telefonní číslo a nápis "Chceš pizzu – zavolej na číslo a objednej". Pro objednání potřebujeme realizovat jednoduchý tok: 

  • Klient navštíví statickou stránku s cenami, vybírá produkty a volá na číslo uvedené na stránce.

  • Klient uvádí produkty, které chce přidat do objednávky.

  • Uvádí svou adresu a jméno.

  • Operátor přijímá objednávku.

  • Objednávka se zobrazuje v rozhraní přijatých objednávek.

Vše začíná zobrazením menu. Přihlášený operátor může v daném okamžiku přijmout pouze jednu objednávku. Proto může draft-košík zůstat v jeho relaci (relace uživatele se uchovává v paměti). Tam je objekt Cart, ve kterém jsou produkty a informace o zákazníkovi.

Zákazník označuje produkt, operátor klikne na + vedle produktu a na server se odešle požadavek. Z produktu se načítají informace z databáze a informace o produktu se přidávají do košíku.

Historie architektury Dodo IS: raný monolit

Poznámka. Ano, zde není nutné načítat produkt z databáze, lze ho předat z frontendu. Ale pro názornost jsem ukázal přesně cestu z databáze. 

Dále zadáme adresu a jméno zákazníka. 

Historie architektury Dodo IS: raný monolit

Po kliknutí na "Vytvořit objednávku":

  • Odesíláme požadavek do OrderController.SaveOrder().

  • Získáváme Cart z relace, tam jsou produkty v potřebném množství.

  • Doplňujeme Cart informacemi o zákazníkovi a předáváme do metody AddOrder třídy ReceivingOrderService, kde je uložen do databáze. 

  • V databázi existují tabulky s objednávkou, obsahem objednávky, zákazníkem a všechny jsou propojené.

  • Rozhraní pro zobrazení objednávky se vyvolává a načítá poslední objednávky a zobrazuje je.

Nové moduly

Přijímání objednávek bylo důležité a nezbytné. Nelze provozovat obchod s prodejem pizzy, pokud neexistuje systém pro přijímání objednávek. Proto systém začal nabobtnávat funkcionalitou — přibližně od roku 2012 do 2015. Během této doby vzniklo mnoho různých bloků systému, které budu nazývat moduly, na rozdíl od pojmu služby nebo produktu. 

Modul je soubor funkcí, které jsou spojeny nějakým společným obchodním cílem. Přitom fyzicky se nacházejí v jedné aplikaci.

Moduly lze nazvat bloky systému. Například to může být modul reportů, rozhraní administrace, sledovač produktů v kuchyni, autorizace. To vše jsou různá uživatelská rozhraní, některé mají dokonce různé vizuální styly. Přitom vše je v rámci jedné aplikace, jednoho pracovního procesu. 

Technicky byly moduly uspořádány jako Area (takový nápad zůstal i v asp.net core). Tam byly oddělené soubory pro frontend, modely, a také vlastní třídy kontrolérů. Výsledkem byla transformace systému z takového...

Historie architektury Dodo IS: raný monolit

...na takový:

Historie architektury Dodo IS: raný monolit

Některé moduly byly realizovány jako samostatné weby (executable project), kvůli zcela odlišnému funkčnímu zaměření a částečně kvůli poněkud oddělenému, více cílenému vývoji. To jsou:

  • Web — první verze webu dodopizza.ru.

  • Export: export reportů z Dodo IS pro 1C. 

  • Osobní — osobní kabinet zaměstnance. Byl vyvíjen samostatně, má svůj vlastní vstup a odlišný design.

  • fs — projekt pro hostování statického obsahu. Později jsme se od něj odklonili a převedli veškerou statiku na CDN Akamai. 

Ostatní bloky se nacházely v aplikaci BackOffice. 

Historie architektury Dodo IS: raný monolit

Vysvětlení názvů:

  • Pokladna — Pokladna restaurace.

  • ShiftManager — rozhraní pro roli „Manažer směny“: aktuální statistiky prodeje pizzerie, možnost zařadit produkty do stoplistu, upravit objednávku.

  • OfficeManager — rozhraní pro roli „Manažer pizzerie“ a „Franchisant“. Zde jsou soustředěny funkce pro nastavení pizzerie, její bonusové akce, příjem a práce se zaměstnanci, zprávy.

  • PublicScreens — rozhraní pro televizory a tablety, které visí v pizzeriích. Na televizorech se zobrazuje menu, reklamní informace a stav objednávky při vydání. 

Používali společnou vrstvu služeb, společný blok doménových tříd Dodo.Core a také společnou databázi. Někdy mohla probíhat interakce mezi jednotlivými weby, jako dodopizza.ru nebo personal.dodopizza.ru.

Při vzniku nových modulů se snažili maximálně znovu využít již vytvořený kód služeb, uložené procedury a tabulky v databázi. 

Pro lepší pochopení rozsahu modulů vyvinutých v systému, zde je schéma z roku 2012 s plány rozvoje:

Historie architektury Dodo IS: raný monolit

Do roku 2015 bylo na schématu a ještě víc v produkci.

  • Přijetí objednávky se proměnilo v samostatný blok Kontaktního Centra, kde objednávku přijímá operátor.

  • Objevily se veřejné obrazovky s menu a informacemi, které visí v pizzeriích.

  • V kuchyni je modul, který automaticky přehrává hlasovou zprávu „Nová pizza“ při přijetí nového objednávky a také tiskne dodací list pro kurýra. To výrazně zjednodušuje procesy v kuchyni a umožňuje zaměstnancům neodvádět pozornost od velkého množství jednoduchých operací.

  • Blok dodávky se stal samostatnou Pokladnou Dodávky, kde byl objednávka vydávána kurýrovi, který se předem přihlásil na směnu. Byla zohledněna jeho pracovní doba pro výpočet mzdy. 

Paralelně mezi lety 2012 a 2015 vzniklo více než 10 vývojářů, otevřelo se 35 pizzerií, systém se rozšířil do Rumunska a připravil se k otevření poboček v USA. Vývojáři již nezajišťovali všechny úkoly, ale byli rozděleni do týmů, z nichž každý se specializoval na svou část systému. 

Problémy

Včetně architektury (ale nejen té).

Chaos v databázi

Jedna databáze je pohodlná. Může dosáhnout konzistence pomocí prostředků zabudovaných v relačních databázích. Práce s ní je obvyklá a snadná, zvláště pokud je tam málo tabulek a málo dat.

Ale za 4 roky vývoje se v databázi zjistilo přibližně 600 tabulek, 1500 uložených procedur, z nichž mnoho obsahovalo i logiku. Bohužel, uložené procedury nenabízí zvláštní výhodu při práci s MySQL. Není je možné кешovat databází a uchovávání logiky v nich komplikuje vývoj a ladění. Opětovné použití kódu je také ztíženo.

Na mnoha tabulkách nebyly vhodné indexy, zatímco jinde bylo naopak příliš mnoho indexů, což ztěžovalo vkládání. Bylo třeba modifikovat přibližně 20 tabulek — transakce na vytvoření objednávky mohla trvat přibližně 3-5 sekundy. 

Data v tabulkách nebyla vždy v nejvhodnější formě. Někde bylo nutné provést denormalizaci. Část pravidelně získávaných dat byla ve sloupci ve formě XML struktury, což prodlužovalo dobu vykonávání, zvyšovalo délku dotazů a komplikovalo vývoj.

Na stejné tabulky se prováděly velmi různé dotazy. Obzvlášť trpěly populární tabulky, jako byla zmíněná tabulka orders nebo tabulka pizzeria. Tyto tabulky se používaly pro výstup operativních rozhraní v kuchyni, analýzu. K nim se obracel také web (dodopizza.ru), k němuž mohlo v každý okamžik přicházet náhle mnoho požadavků. 

Data nebyla agregovaná a mnoho výpočtů probíhalo za běhu pomocí prostředků databáze. To vytvářelo zbytečné výpočty a dodatečné zatížení. 

Často kód chodil do databáze, když to nemusel. Někde chyběly hromadné operace, jinde by bylo potřeba rozdělit jeden dotaz na několik přes kód, aby se urychlila a zvýšila spolehlivost. 

Propojenost a zamotanost v kódu

Moduly, které měly odpovídat za svůj úsek podnikání, to nedělaly poctivě.. Některé z nich měly duplicity funkcí pro role. Například lokální marketér, který odpovídal za marketingové aktivity sítě ve svém městě, musel používat jak rozhraní „Admin“ (pro zakládání akcí), tak rozhraní „Office Manager“ (pro sledování dopadu akcí na podnikání). Samozřejmě, že oba moduly uvnitř používaly jeden servis, který pracoval s bonusovými akcemi.

Služby (třídy v rámci jednoho monolitického velkého projektu) se mohly navzájem volat pro obohacení svých dat.

S těmi samotnými modelovými třídami, které ukládají data, se práce v kódu prováděla různě. Někde byly konstruktory, přes které bylo možné uvést povinná pole. Jinde se to dělalo přes veřejné vlastnosti. Samozřejmě, že získávání a zpracování dat z databáze bylo rozmanité. 

Logika byla buď v controllerech, nebo ve třídách služeb. 

To jsou na první pohled nevýznamné problémy, ale výrazně zpomalovaly vývoj a snižovaly kvalitu, což vedlo k nestabilitě a chybám. 

Složitost velkého vývoje

Obtíže vznikly i při samotném vývoji. Bylo třeba vytvářet různé bloky systému, a to paralelně. Uspokojit potřeby každé komponenty v jednom kódu se stávalo čím dál tím obtížnější. Nebylo jednoduché se dohodnout a potěšit všechny komponenty najednou. K tomu se přidávala omezení technologií, zejména v oblasti databáze a front-endu. Bylo třeba se vzdát JQuery ve prospěch vysoceúrovňových rámců, zejména v části klientských služeb (web).

V některých částech systému by mohly být použity databáze, které byly pro to vhodnější. Například později jsme měli precedent přechodu z Redis na CosmosDB pro ukládání nákupního košíku. 

Týmy a vývojáři, kteří se zabývali svou oblastí, očividně chtěli větší samostatnost pro své služby, jak v oblasti vývoje, tak v oblasti nasazení. Konflikty při mergi, problémy při relezech. Pokud pro 5 vývojářů je tento problém nevýznamný, pro 10, a už vůbec ne při plánovaném růstu, by vše bylo vážnější. A před námi měla být vývoj mobilní aplikace (začal v roce 2017 a v roce 2018 bylo velké selhání). 

Různé části systému vyžadovaly různé ukazatele stability., ale kvůli silné provázanosti systému jsme to nemohli zajistit. Chyba při vývoji nové funkce v administraci mohla klidně ovlivnit příjem objednávek na webu, protože kód je společný a znovu použitelný a databáze a data jsou také jednotné.

Pravděpodobně by bylo možné se v rámci takové monolitně-modulární architektury vyhnout těmto chybám a problémům: rozdělit odpovědnost, provádět refaktoring jak kódu, tak databáze, jasně oddělovat vrstvy od sebe, každý den sledovat kvalitu. Ale zvolená architektonická řešení a zaměření na rychlé rozšiřování funkčnosti systému vedly k problémům v oblasti stability.

Jak blog Síla mysli položil pokladny v restauracích

Pokud by růst sítě pizzerií (a zatížení) pokračoval stejným tempem, pak by za nějakou dobu poklesy byly takové, že by se systém ani nezvedl. Dobře ilustruje problémy, se kterými jsme začali čelit kolem roku 2015, takový příběh. 

Na blogu „Síla mysli“ byl widget, který zobrazoval údaje o tržbách za rok celé sítě. Widget se obracel na veřejné API Dodo, které tyto údaje poskytuje. Nyní jsou tyto statistiky k dispozici na http://dodopizzastory.com/. Widget se zobrazoval na každé stránce a prováděl dotazy po časovači každých 20 sekund. Dotaz měl formu do api.dodopizza.ru a ptal se na:

  • počet pizzerií v síti;

  • celkové tržby sítě od začátku roku;

  • tržby za dnešek.

Dotaz na statistiku o tržbách šel přímo do databáze a začal vyžadovat data o objednávkách, agregovat data přímo za běhu a vydávat součet. 

Do této tabulky objednávek chodily Pokladny v restauracích, exportovaly seznam přijatých objednávek za dnešek, a také se do ní přidávaly nové objednávky. Pokladny prováděly své dotazy každých 5 sekund nebo při obnovení stránky.

Schéma vypadalo takto:

Historie architektury Dodo IS: raný monolit

Jednou na podzim napsal Fjodor Ovčinnikov na svůj blog dlouhý a populární článek. Na blog přišlo mnoho lidí a začali pečlivě číst. Zatímco každý z přítomných četl článek, widget s tržbami spolehlivě pracoval a žádal API každých 20 sekund.

API zavolalo uloženou proceduru pro výpočet součtu všech objednávek od začátku roku pro všechny pizzerie řetězce. Agregace běžela přes tabulku orders, která je velmi populární. Do ní chodí všechny pokladny všech otevřených restaurací v tu dobu. Pokladny přestaly reagovat, objednávky nebyly přijímány. Objednávky rovněž nebyly přijímány ze stránky, neobjevily se na trackeru, směnový manažer je nemohl vidět ve svém rozhraní. 

To není jediný příběh. Do podzimu 2015 byla zátěž na systém kritická každý pátek. Několikrát jsme museli vypnout veřejné API a jednou jsme dokonce museli odpojit web, protože už nic nepomáhalo. Dokonce jsme měli seznam služeb s pořadím odpojení při závažných zátěžích.

Od této doby začíná náš boj se zátěžemi a za stabilizaci systému (od podzimu 2015 do podzimu 2018). Právě tehdy došlo k "Velkému pádu". I dále docházelo k občasným výpadkům, některé byly docela citlivé, ale celkové období nestability nyní můžeme považovat za překonané.

Bouřlivý růst podnikání

Proč nebylo možné "udělat to ihned dobře"? Stačí se podívat na následující grafy.

Historie architektury Dodo IS: raný monolit

Také v letech 2014-2015 došlo k otevření v Rumunsku a bylo připravováno otevření v USA.

Síť rostla velmi rychle, otevíraly se nové země, objevily se nové formáty pizzerií, například se otevřela pizzerie na food courtu. To všechno vyžadovalo značnou pozornost právě k rozšíření funkcí Dodo IS. Bez všech těchto funkcí, bez sledování v kuchyni, sledování produktů a ztrát v systému, zobrazení vydání objednávky v oblasti food courtu, bychom možná nyní nemohli diskutovat o "správné" architektuře a "správném" přístupu k vývoji.

Dalšími překážkami pro včasnou revizi architektury a vůbec pro pozornost k technickým problémům byl krize v roce 2014. Tyto věci těžce zasáhly možnosti pro růst týmů, zejména pro mladé podnikání, jakým byla Dodo Pizza.

Rychlá řešení, která pomohla

Problémy vyžadovaly řešení. Řešení lze zjednodušeně rozdělit do dvou skupin:

  • Rychlá, která hasí požár a poskytují malou rezervu síly a získávají nám čas na změny.

  • Systémová a tudíž, dlouhá. Reengineering řady modulů, dělení monolitické architektury na jednotlivé služby (většina z nich je spíše makroslužby než mikro služby a o tom existují Zpráva Andreje Morevského). 

Suchý seznam rychlých změn je následující:

Zvětšení master databáze

Samozřejmě, prvním krokem v boji s nároky je zvýšení výkonu serveru. To bylo provedeno jak pro master databázi, tak pro webové servery. Bohužel to lze provést pouze do určité míry, poté se to stává příliš drahým.

Od roku 2014 jsme přešli na Azure, o tom jsme také psali v článku „Jak Dodo Pizza dodává pizzu pomocí cloudu Microsoft Azure“. Ale po sérii zvýšení serveru pro databázi jsme narazili na náklady. 

Repliky databáze pro čtení

Byly vytvořeny dvě repliky pro databázi:

ReadReplica pro dotazy na adresáře. Používá se pro čtení adresářů, například měst, ulic, pizzerií, produktů (slowly changed domain), a v těch rozhraních, kde je povolen malý zpoždění. Těchto replik bylo 2, jejichž dostupnost jsme zabezpečili stejně jako u master databáze.

ReadReplica pro dotazy na reporty. Tato databáze měla nižší dostupnost, ale byla využívána pro všechny reporty. Ačkoli měly těžké dotazy na obrovské přepočty dat, nijak neovlivňují master databázi a provozní rozhraní. 

Cache v kódu

V kódu nebylo žádné cache (vůbec). To vedlo k dalším, ne vždy potřebným, dotazům na přetíženou databázi. Cache byly původně jak v paměti, tak na externím cache službě, což byl Redis. Všechno bylo invalidováno časem, nastavení bylo uvedeno v kódu.

Několik serverů pro backend

Backend aplikace bylo také nutné škálovat, aby zvládal zvýšené zatížení. Bylo třeba vytvořit z jednoho iis-serveru cluster. Přenesli jsme sezení aplikací z paměti na RedisCache, což umožnilo vytvořit několik serverů stojících za jednoduchým vyvažovačem zatížení s round robin. Nejprve byl použit ten samý Redis, který byl použit pro cache, poté jsme to rozdělili na několik. 

Nakonec se architektura zkomplikovala…

Historie architektury Dodo IS: raný monolit

…ale část napětí se podařilo uvolnit.

A pak bylo třeba přepracovat přetížené komponenty, což jsme také udělali. O tom budeme hovořit v další části.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster