Vagy minden szerencsétlen vállalat a maga módján boldogtalan.
A Dodo IS rendszer fejlesztése azonnal a Dodo Pizza üzlet kezdete után, 2011-ben indult. Az alapötlet a teljes és totális digitális üzleti folyamatok megvalósítása volt, , ami akkoriban, 2011-ben sok kérdést és szkepticizmust váltott ki. De már 9 éve ezen az úton haladunk – a saját fejlesztésünkkel, ami a monolithból indult.
Ez a cikk a "Miért kell újraíráni az architektúrát és végezni ilyen nagyszabású és hosszú változtatásokat?" kérdésekre ad választ, az előző cikkre . Kezdem azzal, hogyan indult a Dodo IS fejlesztése, hogy nézett ki az eredeti architektúra, hogyan jelentek meg az új modulok, és hogy mely problémák miatt kellett nagyszabású változásokat végrehajtani.

A "Mi az a Dodo IS?" cikk sorozat beszámol az alábbiakról:
A Dodo IS korai monolitja (2011-2015). (Itt vagy Ön)
.
A kliens oldali út: homlokzat az adatbázis felett (2016-2017). (Folyamatban…)
A valódi mikroszolgáltatások története. (2018-2019). (Folyamatban…)
A monolit végleges felosztása és az architektúra stabilizálása. (Folyamatban…)
Eredeti architektúra
2011-ben a Dodo IS architektúrája így nézett ki:

Az első modul az architektúrában a rendelésfelvétel volt. Az üzleti folyamat a következőképpen alakult:
az ügyfél telefonál a pizzériába;
a telefont a menedzser veszi fel;
telefonon átveszi a rendelést;
párhuzamosan adatokat rögzít az rendelésfelvételi felületen: figyelembe veszik az ügyfél adatait, a rendelés részleteit, a szállítási címet.
Az informatikai rendszer felülete körülbelül így nézett ki...
Az első verzió 2011 októberéből:
Az első rendelésfelvételi modul fejlesztésére szánt források korlátozottak voltak. Sok mindent kellett gyorsan és kis csapattal megvalósítani. A kis csapat – 2 fejlesztő – alapozta meg az egész jövőbeli rendszert.
Első döntésük meghatározta a technológiai stack további sorsát:
Backend ASP.NET MVC-n, C# nyelven. A fejlesztők dotnet-esek voltak, ez a stack ismert és kedvelt volt számukra.
Frontend Bootstrap és JQuery segítségével: felhasználói felületek egyedi stílusokkal és szkriptekkel.
MySQL adatbázis: licenszek nélkül, egyszerűen használható.
Szerverek Windows Serveren, mert a .NET akkoriban csak Windows alatt működhetett (a Mono-t nem vitatjuk).
Fizikailag ez mind a „dedikált szerver a szolgáltatónál” formájában nyilvánult meg.
A rendelésfeldolgozó alkalmazás architektúrája
Akkor már mindenki a mikroszolgáltatásokról beszélt, míg a SOA körülbelül 5 évig használatban volt nagyobb projekteknél, például a WCF 2006-ban jelent meg. De akkor megbízható és bevált megoldást választottak.
Itt van.

Az Asp.Net MVC a Razor, amely HTML-oldalt generál a böngészőben a formáról vagy a kliensről érkező kérésre, a szerveren történő rendereléssel. A kliensoldalon a CSS és a JavaScript jeleníti meg az információt, és szükség esetén AJAX-kéréseket hajt végre a JQuery-n keresztül.
A kérdések a szerver felé a *Controller osztályokba érkeznek, ahol a módszer feldolgozza és generálja a végső HTML-oldalt. A kontrollerek kérdéseket intéznek a logikai réteghez, amelyet *Services-nek neveznek. Minden szolgáltatás egy üzleti aspektusért felelt:
Például a DepartmentStructureService információt biztosított a pizzériákról és a részlegekről. A részleg egy fr franchise-kezelés alatt álló pizzéria csoport.
A ReceivingOrdersService fogadta és kiszámolta a rendelés összetételét.
A SmsService SMS-t küldött, API-szolgáltatásokat hívva meg az SMS-küldéshez.
A szolgáltatások adatokat dolgoztak fel az adatbázisból, tárolták az üzleti logikát. Minden szolgáltatásban volt egy vagy több *Repository, azonos nevű. Itt tárolták a lekérdezéseket a tárolt eljárásokra az adatbázisban, és a leképező réteget. A tárolt eljárásokban volt üzleti logika, különösen sok abban, amely riportadatokat szolgáltatott. ORM nem volt használatban, mindenki a kézzel írt SQL-re támaszkodott.
Volt egy domainmodell réteg és közös segédosztályok, például az Order osztály, amely megőrizte a rendelést. Ott volt még a rétegben egy segéd a megjelenítési szöveg valuta szerinti átkonvertálására.
Mindezt egy ilyen modellel lehet elképzelni:

A rendelés folyamata
Vegyük szemügyre az egyszerűsített kezdeti folyamatot egy rendelés létrehozásakor.

Kezdetben az oldal statikus volt. Az árak szerepeltek rajta, és felül a telefonszám és a felirat, hogy „Szeretnél pizzát – hívd fel a számot és rendeld meg”. A rendeléshez egyszerű folyamatot kell megvalósítanunk:
A kliens belép a statikus árazási oldalra, kiválasztja a termékeket és felhívja a weboldalon feltüntetett számot.
A kliens nevében megnevezi azokat a termékeket, amelyeket hozzá szeretne adni a rendeléshez.
Megnevezi a címét és a nevét.
Az operátor elfogadja a rendelést.
A rendelés megjelenik az átvett rendelések felületén.
Minden a menü megjelenítésével kezdődik. A bejelentkezett ügyfél-üzemeltető egy időben csak egy megrendelést fogadhat el. Ezért a draft kosár az ő munkamenetében tárolható (a felhasználó munkamenetét a memória tárolja). Itt található a Cart objektum, amelyben a termékek és az ügyfél információi vannak.
Az ügyfél megnevezi a terméket, az üzemeltető rákattint a + melletti gombra, és a szerverre küld egy kérést. A termékhez információkat nyerünk a kínálatból, és az információt hozzáadjuk a kosárhoz.

Megjegyzés. Igen, itt nem szükséges a terméket megkeresni az adatbázisból, át lehet adni a front-end-ből is. De a világosság kedvéért bemutattam az adatbázis útját.
Ezután megadjuk az ügyfél címét és nevét.

A "Megrendelés létrehozása" gombra kattintva:
A kérést az OrderController.SaveOrder() metódusba küldjük.
Megkapjuk a Cart-ot a munkamenetből, ott a kívánt mennyiségű termék található.
Kiegészítjük a Cart-ot az ügyfél információival, és átadjuk az AddOrder metódusnak a ReceivingOrderService osztályban, ahol az adatbázisba mentjük.
Az adatbázisban vannak táblák a megrendelésekről, a megrendelések összetételéről és az ügyfelekről, ezek mind összekapcsolódnak.
A megrendelés megjelenítője lekérdezi az utolsó megrendeléseket, és megjeleníti azokat.
Új modulok
A megrendelések átvétele fontos és szükséges volt. Nem lehet pizzát árusítani, ha nincs megrendelés átvétele. Ezért a rendszer mindenféle funkcióval bővült — körülbelül 2012 és 2015 között. Ez idő alatt sok különböző rendszerblokk született, amelyeket én moduloknakhívok, ellentétben a szolgáltatás vagy termék fogalmával.
A modul egy funkciókészlet, amelyeket valamilyen közös üzleti cél egyesít. Ezek fizikailag egy alkalmazásban találhatók.
A modulokat a rendszer blokkjainak is nevezhetjük. Például ez a riport modul, az admin felület , az azonosítás. Ezek mind különböző felhasználói felületek, egyesek akár különböző vizuális stílusokkal is rendelkeznek. Mindeközben mindez egyetlen alkalmazás keretein belül, egy működő folyamatban.
Technikailag a modulok Területként (Area) voltak megvalósítva (ez az ötlet a keretrendszerben is megmaradt). Ott külön fájlok voltak a front-endhez, modellekhez, valamint saját vezérlőosztályok. Végül a rendszer átalakult egy olyanból...

...amely így nézett ki:

Néhány modult külön weboldalként valósítottak meg (kivitelezhető projekt), egészen különálló funkcionalitás miatt és részben a részben külön, fókuszáltabb fejlesztés miatt. Ezek:
Weboldal — a dodopizza.ru weboldalnak.
Export: Jelentéskészítés a Dodo IS-ből az 1C számára.
Személyes — a munkavállalók személyes fiókja. Külön lett kifejlesztve, saját belépési ponttal és külön dizájnnal rendelkezik.
fs — projekt statikus tartalomhoz való tárhelyezésre. Később elhagytuk, és az összes statikus tartalmat az Akamai CDN-re helyeztük át.
A többi blokk a BackOffice alkalmazásban található.

A nevek magyarázata:
Cashier — a vendéglátóhely pénztára.
ShiftManager — a „Műszakvezető” szerephez tartozó felületek: az étterem eladási statisztikája, lehetőség termékek letiltására, rendelés módosítása.
OfficeManager — a „Pizzéria vezetője” és a „Franchise-tag” szerephez tartozó felületek. Itt található a pizzéria beállításával, bónusz akcióival, munkavállalók fogadásával és kezelésével, valamint jelentésekkel kapcsolatos funkciók.
PublicScreens — a pizzériákban elhelyezett televíziók és táblagépek felületei. A televíziókon menü, reklám információk, valamint a rendelés állapota jelenik meg a kiadás során.
Közös szolgáltatásréteget, közös Dodo.Core domain osztály blokkot és közös adatbázist használtak. Néha a különböző oldalak, például a dodopizza.ru vagy a personal.dodopizza.ru közötti átkapcsolások is léteztek.
Új modulok megjelenésekor igyekeztek minél inkább újrahasznosítani a már létrehozott szolgáltatásokat, tárolt eljárásokat és adatbázis táblákat.
A rendszerben készült modulok méretének jobb megértése érdekében itt van egy 2012-es séma a fejlesztési tervekkel:

2015-re minden a sémán, és még annál is több éles üzemmódba került.
A rendelésfelvétel külön blokká fejlődött, a Kontakt Központba, ahol a rendelést egy operátor veszi fel.
Megjelentek a pizzériákban lógó, nyilvános menüvel és információval rendelkező képernyők.
A konyhában van egy modul, amely automatikusan lejátsza a hangüzenetet: „Új pizza”, amikor új rendelés érkezik, és nyomtatja a fuvarlevelet a futár számára. Ez jelentősen megkönnyíti a konyhai folyamatokat, lehetővé téve, hogy a munkavállalók ne vonják el figyelmüket sok egyszerű műveletről.
A szállítási blokk külön Kiszállító Pénztárrá vált, ahol a rendelést a futár kapta meg, aki előzetesen a műszakra jelentkezett. Figyelembe vették a munkavégzési idejét a bérszámfejtéshez.
2012 és 2015 között párhuzamosan több mint 10 fejlesztő jelent meg, 35 pizzéria nyílt meg, a rendszert Romániában elindították, és az Egyesült Államokban is készültek a nyitásra. A fejlesztők már nem foglalkoztak minden feladattal, hanem csapatokra oszlottak, mindegyik a rendszer saját részén dolgozott.
Problems
Egyebek mellett az architektúra miatt (de nem csak).
Káosz az adatbázisban
Egy adatbázis — ez kényelmes. Itt következetességet lehet elérni, ráadásul a relációs adatbázisokba beépített eszközök révén. Dolgozni vele szokásos és kényelmes, különösen, ha kevés táblázat és nem sok adat van benne.
De négy év fejlesztés után az adatbázisban körülbelül 600 táblázat és 1500 tárolt eljárás volt, amelyek közül sokban még logika is volt. Sajnos a tárolt eljárások nem nyújtanak különösebb előnyt a MySQL használatakor. Az adatbázis nem gyorsítja őket, és a logika tárolása bonyolítja a fejlesztést és a hibakeresést. A kód újrahasználása is nehézkes volt.
Sok táblázaton nem voltak megfelelő indexek, míg máshol éppen ellenkezőleg, túl sok index volt, ami megnehezítette az adatok beillesztését. Körülbelül 20 táblát kellett módosítani — a tranzakció egy rendelés létrehozására körülbelül 3-5 másodpercig tartott.
Az adatbázisban nem mindig voltak az adatok a legmegfelelőbb formában. Valahol denormalizálásra volt szükség. A rendszeresen kapott adatok egy része XML-struktúrában volt egy oszlopban, ami növelte a végrehajtási időt, meghosszabbította a lekérdezéseket és bonyolította a fejlesztést.
Ugyanazokhoz a táblázatokhoz nagyon különböző lekérdezések érkeztek. Különösen szenvedtek a népszerű táblázatok, mint a fent említett tábla orders vagy a pizzeria. Ezeket az operatív felületek kiadására használták a konyhán, elemzés céljából. Emellett a weboldal is hivatkozott rájuk (), amelyhez bármikor hirtelen sok lekérdezés érkezhetett.
Az adatok nem voltak aggregáltak és sok számítás valós időben történt az adatbázis eszközeivel. Ez felesleges számításokat és extra terhelést okozott.
Gyakran a kód az adatbázisba lépett, amikor nem kellett volna. Valahol hiányoztak a bulk-műveletek, máshol egy lekérdezést több részre kellett volna bontani a gyorsaság és a megbízhatóság növeléséhez.
A kód összefonódása és bonyolultsága
A modulok, amelyeknek felelniük kellett volna a saját üzleti területükért, ezt nem tették meg megfelelően.. Néhány funkciót duplikálva tartalmaztak a szerepek. Például a helyi marketinges, aki a városában a hálózat marketingtevékenységéért felelős, kénytelen volt használni mind az „Admin” felületet (akciók létrehozásához), mind pedig a „Központi Menedzser” felületet (az akciók üzletre gyakorolt hatásának megtekintéséhez). Természetesen mindkét modul egy közös szolgáltatást használt, amely a bónusz akciókkal foglalkozott.
A szolgáltatások (osztályok egy monolitikus nagy projekt keretein belül) egymást hívhatták az adataik gazdagítására.
A modellekhez tartozó osztályok, amelyek az adatokat tárolták, a kódban eltérően működtek. Valahol voltak konstruktőrök, amelyeken keresztül kötelező mezőket lehetett megadni. Máshol ez nyilvános tulajdonságokon keresztül történt. Természetesen az adatok lekérése és átalakítása az adatbázisból változatos volt.
A logika vagy a vezérlőkben, vagy a szolgáltatásosztályokban volt.
Ezek látszólag jelentéktelen problémák voltak, de komolyan lelassították a fejlesztést és csökkentették a minőséget, ami instabilitáshoz és hibákhoz vezetett.
A nagy fejlesztési bonyolultság
Nehézségek merültek fel magában a fejlesztésben. Különböző rendszerblokkokat kellett készíteni, ráadásul párhuzamosan. A szükségletek egyetlen kódba való összeszorítása egyre nehezebbé vált. Nem volt egyszerű megegyezni és egyszerre mindenkinek megfelelni. Ezen kívül technológiákból eredő korlátozások is voltak, különösen az adatbázisra és a frontend-re vonatkozóan. A JQueryről el kellett hagyni a magas szintű keretrendszerek felé, különösen az ügyfélszolgáltatások (weboldal) vonatkozásában.
A rendszer bizonyos részein azok az adatbázisok hasznosabbak lehettek,. Például később volt precedensünk a Redis-ról a CosmosDB-ra való áttérésre a kosár tárolásához.
A csapatok és a fejlesztők, akik saját területüket kezelték, egyértelműen nagyobb önállóságot akartak szolgáltatásaik fejlesztésében és üzembe helyezésében. Összefonódási konfliktusok, kiadási problémák. Ha 5 fejlesztő számára ez a probléma lényegtelen, akkor 10-nél, és különösen a tervezett növekedésnél, a helyzet komolyan súlyosbodik. Ráadásul előttük mobilalkalmazás fejlesztése volt (ez 2017-ben indult, és 2018-ban volt ).
A rendszer különböző részei eltérő stabilitási mutatókat igényeltek., de a rendszer szoros összefonódása miatt ezt nem tudtuk biztosítani. Egy új funkció fejlesztése a vezérlőpulton problémát okozhatott a weboldalon leadott rendelések során, hiszen a kód közös és újrahasználható, az adatbázis és az adatok is egységesek.
Valószínűleg a monolitikus-moduláris architektúra keretein belül is elkerülhettük volna ezeket a hibákat és problémákat: a felelősség megosztása, a kód és az adatbázis refaktorálása, a rétegek világos elválasztása, a minőség napi nyomon követése. Azonban a választott architekturális megoldások és a rendszer funkcionalitásának gyors bővítésére való fókuszálás instabilitási problémákhoz vezetett.
A „Szellemi Erő” blog jelentősége a éttermek kasszáiban
Ha a pizzéria-hálózat (és a terhelés) növekedése ugyanebben a tempóban folytatódott volna, akkor egy idő után a visszaesések olyan súlyossá váltak volna, hogy a rendszer már nem tudott volna működni. Ezt jól illusztrálja 2015 körüli problémáink egy ilyen története.
A „” blogon volt egy widget, ami az egész hálózat éves bevételi adatait mutatta. A widget a Dodo nyilvános API-jához fordult, amely ezeket az adatokat biztosítja. Jelenleg ez a statisztika elérhető a . A widget minden oldalon megjelent, és 20 másodpercenként kérte az adatokat. A kérés a api.dodopizza.ru-ra ment, és az alábbiakat kérte:
a hálózatban lévő pizzériák száma;
a hálózat összes bevétele az év eleje óta;
a mai napi bevétel.
A bevételi statisztika iránti kérés közvetlenül az adatbázisba ment, és elkezdett adatokat lekérdezni a rendelésekből, aggregálta az adatokat valós időben és kiadta az összeget.
Ebben a rendelés táblázatban a kasszák az éttermekben is belefértek, és letöltötték a ma elfogadott rendelések listáját, az új rendelések is ide kerültek. A kasszák minden 5 másodpercben, vagy az oldal frissítésekor kértek.
A rendszer így nézett ki:

Egy ősszel, Fedor Ovcsinnyikov egy hosszú és népszerű cikket írt a blogjára. A blogra nagyon sok ember jött, és figyelmesen mindent olvastak. Miközben minden látogató olvasta a cikket, a bevételi widget zavartalanul működött, és 20 másodpercenként lekérdezte az API-t.
Az API a hálózat összes pizzériájának rendeléseinek összegének számítására szolgáló tárolt eljárást hívott meg az év elejétől kezdve. Az aggregálás az orders táblán történt, ami nagyon népszerű. Ebben tárolják az összes nyitott étterem kasszáját abban az időpontban. A kasszák nem válaszoltak, a rendeléseket nem fogadták el. A weboldalról sem fogadtak el rendeléseket, ezek nem jelentek meg a nyomkövetőn, a váltásvezető nem tudta őket látni a felületén.
Ez nem az egyetlen történet. 2015 őszére minden pénteken kritikus volt a rendszer terhelése. Többször is le kellett állítanunk a nyilvános API-t, egyszer pedig akár a weboldalt is ki kellett kapcsolnunk, mert már semmi sem segített. Volt egy lista a szolgáltatásokról, amelyek sorrendben történtek a súlyos terhelések során.
Ettől az időponttól kezdődik a harcunk a terhelésekkel és a rendszer stabilizálásával (2015 ősze és 2018 ősze között). Akkor történt a "". Ezek után is előfordultak hibák, amelyek közül néhány meglehetősen érzékeny volt, de most már az általános instabilitási időszakot letudhatjuk.
A vállalkozás robbanásszerű növekedése
Miért nem lehetett "azonnal jól csinálni"? Elég csak ránézni a következő grafikonokra.

2014-2015-ben Romániában nyitottunk, és az Egyesült Államokban is nyitásra készültünk.
A hálózat nagyon gyorsan nőtt, új országok nyíltak, új pizzéria formátumok jelentek meg, például megnyílt egy pizzéria a food courtban. Mindez jelentős figyelmet követelt meg a Dodo IS funkcióinak bővítésére. Mindezek nélkül, a konyhai nyomkövetés, az alapanyagok és veszteségek figyelése a rendszerben, valamint a rendelés megjelenítése a food court teremben, valószínűleg most nem beszélnénk a "helyes" architektúráról és a "helyes" fejlesztési megközelítésről.
A 2014-es válság is akadályozta az architektúra időben történő felülvizsgálatát és a technikai problémákra való figyelmet. Az ilyen dolgok fájdalmasan érintik a csapatok növekedési lehetőségeit, különösen egy fiatal vállalkozás számára, amilyen a Dodo Pizza volt.
Gyors megoldások, amelyek segítettek
A problémák megoldást követeltek. Általánosságban a megoldásokat két csoportra lehet osztani:
Gyorsak, amelyek eloltják a tüzet, és kis tartalékot adnak, így időt nyerünk a változásokra.
Rendszerszintű és ezért hosszú távúak. Számos modul újratervezése, a monolitikus architektúra különálló szolgáltatásokra való bontása (ezek közül a legtöbb valójában nem micro, inkább macro szolgáltatás és ezeket említettük) ).
A gyors változások száraz listája a következő:
Mester adatbázis skálázása
Természetesen az első lépés a terhelések kezelésére a szerver teljesítményének növelése. Ezt elkészítettük a mester adatbázis és a web szerverek számára. Sajnos ez csak bizonyos határokig lehetséges, utána túl drágává válik.
2014 óta átálltunk az Azure-ra, erről már akkor írtunk a "" cikkünkben. De a mester adatbázis szervereinek sorozatos növelések után elakadtunk a költségeken.
Olvasási másolatok az adatbázisra
Két másolatot hoztunk létre az adatbázis számára:
ReadReplica szótárolási lekérdezésekhez. A szótárak, például városok, utcák, pizzériák, termékek (lassan változó domain), és azokon a felületeken használatos, ahol elfogadható egy kis késés. Ezekből a másolatokból kettőt állítottunk be, és biztosítottuk az elérhetőségüket, mint a mester adatbázis esetében.
ReadReplica riportkérésekhez. Ez az adatbázis alacsonyabb elérhetőséggel rendelkezett, de ide futottak be az összes riport. Bár nehéz lekérdezések voltak óriási adatszámításokkal, nem befolyásolták a fő adatbázist és az operációs felületeket.
Készletek a kódban
A kódban sehol nem voltak készletek. Ez plusz, nem mindig szükséges lekérdezésekhez vezetett a terhelt adatbázisban. A készleteket kezdetben memóriában és egy külső gyorsítószolgáltatásban, a Redis-ben tároltuk. Minden idővel érvénytelenítődött, a beállításokat a kódban adtuk meg.
Több szerver a háttérrendszerhez
A háttérrendszert is skálázni kellett a növekvő terhelés elviseléséhez. Az egy iis szerverből klónt kellett létrehozni. Áttelepítettük a RedisCache-be, ami lehetővé tette több szerver létrehozását egy egyszerű terheléselosztó mögött, körkörös elosztással. Eleinte ugyanazt a Redis-t használtuk a gyorsítótárakhoz, majd szétválasztottuk több szerverre.
Végül az architektúra bonyolultabbá vált...

...de egy részét a feszültségnek sikerült csökkenteni.
Ezután a terhelt komponenseket kellett átalakítanunk, amivel nekiláttunk. Erről a következő részben fogunk beszámolni.
Forrás: habr.com
