Architectural History of Dodo IS: An Early Monolith

Eller varje olyckligt företag med en monolit är olyckligt på sitt sätt.

Utvecklingen av Dodo IS-systemet började omedelbart, liksom Dodo Pizza-verksamheten - 2011. Det baserades på idén om fullständig och total digitalisering av affärsprocesser, och på egen hand, vilket redan då 2011 väckte många frågor och skepsis. Men i 9 år nu har vi följt denna väg - med vår egen utveckling, som började med en monolit.

Den här artikeln är "svaret" på frågorna "Varför skriva om arkitekturen och göra så storskaliga och långsiktiga förändringar?" till föregående artikel "Historien om Dodo IS-arkitekturen: backofficets väg". Jag börjar med hur utvecklingen av Dodo IS började, hur den ursprungliga arkitekturen såg ut, hur nya moduler dök upp och på grund av vilka problem storskaliga förändringar var tvungna att göras.

Architectural History of Dodo IS: An Early Monolith

Artikelserie "Vad är Dodo IS?" kommer att berätta om:

  1. Tidig monolit i Dodo IS (2011-2015). (Du är här)

  2. Backoffice-väg: separata baser och buss.

  3. Klientdelens väg: fasad ovanför basen (2016-2017). (Pågående...)

  4. Historien om riktiga mikrotjänster. (2018-2019). (Pågående...)

  5. Färdig skärning av monoliten och stabilisering av arkitekturen. (Pågående...)

Ursprunglig arkitektur

2011 såg Dodo IS-arkitekturen ut så här:

Architectural History of Dodo IS: An Early Monolith

Den första modulen i arkitekturen är orderacceptans. Affärsprocessen såg ut så här:

  • en kund ringer pizzerian;

  • Chefen lyfter telefonen;

  • tar emot beställningar via telefon;

  • Samtidigt fyller den i orderacceptansgränssnittet: information om kunden, uppgifter om orderdetaljer och leveransadress beaktas. 

Informationssystemets gränssnitt såg ut ungefär så här...

Första versionen från oktober 2011:

Något förbättrad i januari 2012

Informationssystem Dodo Pizza Leverans Pizzarestaurang

Resurserna för att utveckla den första ordertagningsmodulen var begränsade. Det var nödvändigt att göra mycket, snabbt och med ett litet team. Det lilla teamet består av 2 utvecklare som lagt grunden för hela det framtida systemet.

Deras första beslut avgjorde teknologistackens framtida öde:

  • Backend på ASP.NET MVC, C#-språk. Utvecklarna var dotnetters, denna stack var bekant och trevlig för dem.

  • Frontend på Bootstrap och JQuery: användargränssnitt baserat på anpassade stilar och skript. 

  • MySQL-databas: inga licenskostnader, lätt att använda.

  • Servrar på Windows Server, eftersom .NET då bara kunde finnas på Windows (vi kommer inte att diskutera Mono).

Fysiskt uttrycktes allt detta i "värdens skrivbord". 

Arkitektur för ansökan om orderacceptans

Då pratade alla redan om mikrotjänster och SOA hade använts i stora projekt i cirka 5 år, till exempel släpptes WCF 2006. Men sedan valde de en pålitlig och beprövad lösning.

Här är det.

Architectural History of Dodo IS: An Early Monolith

Asp.Net MVC är Razor, som på begäran från ett formulär eller från en klient producerar en HTML-sida med rendering på servern. På klienten visar CSS- och JS-skript redan information och vid behov utför AJAX-förfrågningar via JQuery.

Förfrågningar på servern faller i *Controller-klasserna, där metoden bearbetar och genererar den slutliga HTML-sidan. Kontrollanter gör förfrågningar till ett lager av logik som kallas *Tjänster. Var och en av tjänsterna motsvarade någon aspekt av verksamheten:

  • Till exempel gav DepartmentStructureService information om pizzerior och avdelningar. En avdelning är en grupp pizzerior som drivs av en franchisetagare.

  • ReceivingOrdersService tog emot och beräknade innehållet i beställningen.

  • Och SmsService skickade SMS genom att ringa API-tjänster för att skicka SMS.

Tjänsterna bearbetade data från databasen och lagrade affärslogik. Varje tjänst hade ett eller flera *Repositories med lämpligt namn. De innehöll redan frågor till lagrade procedurer i databasen och ett lager av mappare. Lagren hade affärslogik, särskilt många av dem som producerade rapporteringsdata. ORM användes inte, alla förlitade sig på handskriven sql. 

Det fanns också ett lager av domänmodellen och allmänna hjälpklasser, till exempel klassen Order, som lagrade beställningen. Där, i lagret, fanns en hjälpreda för att konvertera displaytext enligt vald valuta.

Allt detta kan representeras av denna modell:

Architectural History of Dodo IS: An Early Monolith

Beställ sätt

Låt oss överväga ett förenklat initialt sätt att skapa en sådan beställning.

Architectural History of Dodo IS: An Early Monolith

Till en början var sajten statisk. Det fanns priser på den, och högst upp fanns ett telefonnummer och inskriptionen "Om du vill ha pizza, ring numret och beställ." För att beställa måste vi implementera ett enkelt flöde: 

  • Kunden går till en statisk webbplats med priser, väljer produkter och ringer numret som står på webbplatsen.

  • Kunden namnger de produkter han vill lägga till i beställningen.

  • Uppger hans adress och namn.

  • Operatören accepterar beställningen.

  • Beställningen visas i gränssnittet för accepterade beställningar.

Det hela börjar med menyvisningen. En inloggad operatörsanvändare accepterar endast en beställning åt gången. Därför kan utkastvagnen lagras i sin session (användarens session lagras i minnet). Det finns ett Cart-objekt som innehåller produkter och kundinformation.

Kunden namnger produkten, operatören klickar på + bredvid produkten, och en förfrågan skickas till servern. Information om produkten dras ut från databasen och information om produkten läggs i varukorgen.

Architectural History of Dodo IS: An Early Monolith

Notera. Ja, här behöver du inte dra ut produkten från databasen, utan överföra den från fronten. Men för tydlighetens skull visade jag exakt vägen från basen. 

Ange sedan kundens adress och namn. 

Architectural History of Dodo IS: An Early Monolith

När du klickar på "Skapa beställning":

  • Vi skickar förfrågan till OrderController.SaveOrder().

  • Vi får Cart från sessionen, det finns produkter i den mängd vi behöver.

  • Vi kompletterar Cart med information om klienten och skickar den till AddOrder-metoden i klassen ReceivingOrderService, där den sparas i databasen. 

  • Databasen har tabeller med ordern, innehållet i ordern, klienten och de är alla anslutna.

  • Ordervisningsgränssnittet går och drar ut de senaste beställningarna och visar dem.

Nya moduler

Att ta emot beställningen var viktigt och nödvändigt. Du kan inte driva en pizzaaffär om du inte har en beställning att sälja. Därför började systemet få funktionalitet - från cirka 2012 till 2015. Under denna tid dök många olika block av systemet upp, som jag kommer att kalla moduler, i motsats till begreppet tjänst eller produkt. 

En modul är en uppsättning funktioner som förenas av något gemensamt affärsmål. Dessutom är de fysiskt placerade i samma applikation.

Moduler kan kallas systemblock. Detta är till exempel en rapporteringsmodul, admin-gränssnitt, spårare för köksprodukter, auktorisation. Dessa är alla olika användargränssnitt, vissa har till och med olika visuella stilar. Dessutom finns allt inom en applikation, en pågående process. 

Tekniskt sett designades modulerna som Area (denna idé fanns till och med kvar i asp.net kärna). Det fanns separata filer för frontend, modeller, såväl som deras egna kontrollerklasser. Som ett resultat omvandlades systemet från sådana...

Architectural History of Dodo IS: An Early Monolith

...till detta:

Architectural History of Dodo IS: An Early Monolith

Vissa moduler implementeras av separata platser (körbart projekt), på grund av helt separat funktionalitet och delvis på grund av något separat, mer fokuserad utveckling. Detta:

  • Plats - första versionen webbplats dodopizza.ru.

  • Exportera: ladda ner rapporter från Dodo IS för 1C. 

  • Personlig – anställds personliga konto. Den utvecklades separat och har sin egen ingångspunkt och separat design.

  • fs — Projekt för statisk värd. Senare flyttade vi bort från det och flyttade allt statiskt innehåll till Akamai CDN. 

De återstående blocken fanns i BackOffice-applikationen. 

Architectural History of Dodo IS: An Early Monolith

Förklaring av namn:

  • Kassa - Restaurangkassa.

  • ShiftManager - gränssnitt för rollen "Shift Manager": driftstatistik över pizzeriaförsäljning, möjligheten att sätta produkter på en stopplista, ändra en beställning.

  • OfficeManager - gränssnitt för rollerna "Pizzeria Manager" och "Franchisetagare". Här kan du hitta funktioner för att sätta upp en pizzeria, dess bonuskampanjer, ta emot och arbeta med anställda samt rapporter.

  • PublicScreens - gränssnitt för TV-apparater och surfplattor som hänger i pizzerior. TV-apparaterna visar menyn, reklaminformation och orderstatus vid leverans. 

De använde ett gemensamt tjänstelager, ett gemensamt block av Dodo.Core-domänklasser och en gemensam bas. Ibland kunde de fortfarande leda genom passager till varandra. Dessutom har enskilda webbplatser, som dodopizza.ru eller personal.dodopizza.ru, också tillgång till vanliga tjänster.

När nya moduler dök upp försökte vi återanvända så mycket som möjligt av den redan skapade koden för tjänster, lagrade procedurer och tabeller i databasen. 

För en bättre förståelse av omfattningen av moduler som gjorts i systemet, här är ett diagram från 2012 med utvecklingsplaner:

Architectural History of Dodo IS: An Early Monolith

År 2015 var allt på rätt spår och ännu mer var i produktion.

  • Orderacceptans har vuxit till ett separat block av Contact Center, där ordern accepteras av operatören.

  • Offentliga skärmar med menyer och information har dykt upp på pizzerior.

  • Köket har en modul som automatiskt spelar upp ett röstmeddelande "Ny pizza" när en ny beställning kommer, och även skriver ut en faktura till budet. Detta förenklar processer i köket avsevärt och gör att anställda inte kan distraheras av ett stort antal enkla operationer.

  • Leveransblocket blev en separat Delivery Cashier, där beställningen utfärdades till kuriren, som tidigare tagit sitt skift. Hans arbetstid togs med i beräkningen av hans lön. 

Parallellt, från 2012 till 2015, dök mer än 10 utvecklare upp, 35 pizzerior öppnade, systemet distribuerades till Rumänien och förbereddes för öppnandet av poäng i USA. Utvecklare var inte längre involverade i alla uppgifter, utan var indelade i team. var och en specialiserad på sin egen del av systemet. 

Problem

Inklusive på grund av arkitekturen (men inte bara).

Kaos i basen

En bas är bekvämt. Det är möjligt att uppnå konsekvens, och på bekostnad av verktyg inbyggda i relationsdatabaser. Att arbeta med det är bekant och bekvämt, särskilt om det finns få tabeller och lite data.

Men under 4 års utveckling innehöll databasen cirka 600 tabeller, 1500 lagrade procedurer, varav många också hade logik. Tyvärr ger lagrade procedurer inte mycket nytta när man arbetar med MySQL. De cachelagras inte av databasen, och att lagra logik i dem komplicerar utveckling och felsökning. Att återanvända kod är också svårt.

Många tabeller hade inte lämpliga index, någonstans, tvärtom, fanns det många index, vilket gjorde insättningen svår. Cirka 20 tabeller behövde modifieras – transaktionen för att skapa en beställning kunde ta cirka 3-5 sekunder. 

Data i tabellerna var inte alltid i den mest lämpliga formen. Någonstans var det nödvändigt att göra denormalisering. En del av de regelbundet mottagna uppgifterna fanns i en kolumn i form av en XML-struktur, vilket ökade exekveringstiden, förlängde frågorna och komplicerade utvecklingen.

Samma tabeller var föremål för mycket heterogena förfrågningar. Populära bord, som tabellen ovan, var särskilt drabbade ordrar eller tabeller pizzeria. De användes för att visa operativa gränssnitt i köket och analyser. Sajten kontaktade dem också (dodopizza.ru), där många förfrågningar plötsligt kan komma när som helst. 

Data samlades inte ihop och många beräkningar skedde i farten med hjälp av basen. Detta skapade onödiga beräkningar och extra belastning. 

Ofta gick koden in i databasen när den inte kunde ha gjort det. Någonstans saknades bulkoperationer, någonstans skulle det vara nödvändigt att dela upp en begäran i flera genom kod för att snabba upp och öka tillförlitligheten. 

Sammanhållning och förvirring i kod

Modulerna som skulle stå för sin del av verksamheten gjorde det inte ärligt. Några av dem hade dubblering av funktioner för roller. Till exempel, en lokal marknadsförare som är ansvarig för marknadsföringsaktiviteten i ett nätverk i sin stad var tvungen att använda både "Admin"-gränssnittet (för att ställa in kampanjer) och "Office Manager"-gränssnittet (för att se kampanjernas inverkan på företag). Naturligtvis använde båda modulerna samma tjänst, som fungerade med bonuskampanjer.

Tjänster (klasser inom ett monolitiskt stort projekt) kan ringa varandra för att berika sina data.

Med själva modellklasserna som lagrar data, arbetet i koden utfördes annorlunda. Någonstans fanns det konstruktörer genom vilka man kunde ange obligatoriska fält. Någonstans skedde detta genom allmänna fastigheter. Naturligtvis var det varierat att hämta och transformera data från databasen. 

Logiken var antingen i kontroller eller serviceklasser. 

Dessa verkar vara mindre problem, men de saktade ner utvecklingen avsevärt och minskade kvaliteten, vilket ledde till instabilitet och buggar. 

Komplexiteten i stor utveckling

Det uppstod svårigheter i själva utvecklingen. Det var nödvändigt att skapa olika block av systemet, och parallellt. Det blev allt svårare att anpassa behoven för varje komponent i en enda kod. Det var inte lätt att komma överens och behaga alla komponenter på samma gång. Till detta kom begränsningar i tekniken, särskilt när det gäller basen och fronten. Det var nödvändigt att överge JQuery till förmån för ramverk på hög nivå, särskilt när det gäller kundtjänster (webbplats).

Vissa delar av systemet skulle kunna använda databaser som är mer lämpade för detta. Till exempel fick vi senare ett prejudikat för att byta från Redis till CosmosDB för att lagra en ordervagn. 

Team och utvecklare som arbetar i deras område ville helt klart ha mer oberoende för sina tjänster, både när det gäller utveckling och när det gäller utbyggnad. Konflikter under sammanslagningar, problem under releaser. Om detta problem är obetydligt för 5 utvecklare, skulle allt bli allvarligare med 10, och ännu mer med den planerade tillväxten. Och vad som skulle komma var utvecklingen av en mobilapplikation (den startade 2017 och 2018 fanns det stort fall). 

Olika delar av systemet krävde olika stabilitetsindikatorer, men på grund av systemets starka anslutning kunde vi inte tillhandahålla detta. Ett fel vid utveckling av en ny funktion i adminpanelen kunde mycket väl ha resulterat i orderacceptans på sajten, eftersom koden är vanlig och återanvändbar, databasen och data är också desamma.

Det skulle förmodligen vara möjligt att undvika dessa fel och problem inom ramen för en sådan monolitisk-modulär arkitektur: skapa en ansvarsuppdelning, refaktorera både koden och databasen, separera tydligt lager från varandra och övervaka kvaliteten varje dag. Men de valda arkitektoniska lösningarna och fokus på att snabbt utöka systemets funktionalitet ledde till problem i frågor om stabilitet.

Hur bloggen Power of Mind satte kassaapparater på restauranger

Om tillväxten av pizzerianätverket (och belastningen) fortsatte i samma takt, skulle dropparna efter ett tag vara sådana att systemet inte skulle återhämta sig. Följande berättelse illustrerar väl de problem som vi började möta 2015. 

I bloggen"Sinnekraft"det fanns en widget som visade intäktsdata för året för hela nätverket. Widgeten fick åtkomst till det offentliga Dodo API, som tillhandahåller dessa data. Denna statistik finns nu tillgänglig på http://dodopizzastory.com/. Widgeten visades på varje sida och gjorde förfrågningar på en timer var 20:e sekund. Förfrågan gick till api.dodopizza.ru och frågade:

  • antal pizzerior i nätverket;

  • totala nätverksintäkter sedan början av året;

  • dagens intäkter.

En begäran om intäktsstatistik gick direkt till databasen och började begära uppgifter om order, aggregera data direkt i farten och utfärda beloppet. 

Kassaapparater på restauranger gick till samma beställningstabell, laddade upp en lista över beställningar som accepterats för idag och nya beställningar lades till. Kassorna gjorde sina förfrågningar var 5:e sekund eller när sidan uppdaterades.

Diagrammet såg ut så här:

Architectural History of Dodo IS: An Early Monolith

En dag på hösten skrev Fjodor Ovchinnikov en lång och populär artikel på sin blogg. Många kom till bloggen och började läsa allt noga. Medan var och en av personerna som kom läste artikeln, fungerade intäktswidgeten korrekt och begärde API var 20:e sekund.

API kallade en lagrad procedur för att beräkna mängden av alla beställningar sedan början av året för alla pizzerior i nätverket. Aggregeringen baserades på ordertabellen, som är mycket populär. Alla kassor på alla öppna restauranger på den tiden går till den. Kassorna slutade svara och beställningar accepterades inte. De accepterades inte heller från sajten, syntes inte på spåraren och skiftchefen kunde inte se dem i sitt gränssnitt. 

Detta är inte den enda historien. Hösten 2015 var belastningen på systemet kritisk varje fredag. Flera gånger stängde vi av det offentliga API:t, och en gång var vi till och med tvungna att stänga av webbplatsen eftersom ingenting hjälpte. Det fanns till och med en lista över tjänster med ordningen för avstängning under tunga belastningar.

Från och med denna tidpunkt börjar vår kamp med laster och för systemstabilisering (från hösten 2015 till hösten 2018). Det var då det hände"Den stora hösten" Vidare fanns det ibland också misslyckanden, av vilka några var mycket känsliga, men den allmänna perioden av instabilitet kan nu anses vara över.

Snabb affärstillväxt

Varför kunde det inte "göras bra direkt"? Titta bara på följande grafer.

Architectural History of Dodo IS: An Early Monolith

Även 2014-2015 var det en öppning i Rumänien och en öppning i USA höll på att förberedas.

Kedjan växte väldigt snabbt, nya länder öppnade, nya format av pizzerior dök upp, till exempel öppnade en pizzeria i food courten. Allt detta krävde stor uppmärksamhet specifikt för att utöka funktionerna i Dodo IS. Utan alla dessa funktioner, utan spårning i köket, redovisning av produkter och förluster i systemet, visning av leverans av beställningar i food courthallen, är det osannolikt att vi nu skulle prata om den "rätta" arkitekturen och den "rätta" ” inställning till utveckling.

Ett annat hinder för en snabb översyn av arkitekturen och allmän uppmärksamhet på tekniska problem var krisen 2014. Sådana saker skadar tillväxtmöjligheterna för team, särskilt för ett ungt företag som Dodo Pizza.

Snabba lösningar som hjälpte

Problem behövde lösningar. Konventionellt kan lösningar delas in i två grupper:

  • Snabba sådana som släcker elden och ger oss en liten säkerhetsmarginal och ger oss tid att byta om.

  • Systemisk och därför lång. Omkonstruera ett antal moduler, dela upp den monolitiska arkitekturen i separata tjänster (de flesta av dem är inte mikrotjänster utan snarare makrotjänster, och det finns mer om detta rapport av Andrey Morevsky). 

Den torra listan över snabba ändringar är:

Skala upp basmästaren

Naturligtvis är det första som görs för att bekämpa belastningen att öka serverkraften. Detta gjordes för huvuddatabasen och webbservrarna. Tyvärr är detta bara möjligt upp till en viss gräns, utöver det blir det för dyrt.

Sedan 2014 har vi flyttat till Azure; vi skrev också om detta ämne då i artikeln "Hur Dodo Pizza levererar pizza med hjälp av Microsoft Azure-molnet" Men efter en serie serverökningar nådde kostnaden för basen en gräns. 

Databasrepliker för läsning

Vi gjorde två repliker för basen:

Läs Replika för katalogförfrågningar. Den används för att läsa kataloger, såsom stad, gata, pizzeria, produkter (långsamt ändrade domän), och i de gränssnitt där en liten fördröjning är acceptabel. Det fanns 2 av dessa repliker, vi säkerställde deras tillgänglighet på samma sätt som mastern.

Läs Replika för rapportförfrågningar. Denna databas hade lägre tillgänglighet, men alla rapporter gick till den. De kan ha tunga förfrågningar om enorma dataomräkningar, men de påverkar inte huvuddatabasen och operativa gränssnitt. 

Cachar i kod

Det fanns inga cacher någonstans i koden (överhuvudtaget). Detta ledde till ytterligare, inte alltid nödvändiga, förfrågningar till den laddade databasen. Till en början fanns cacher både i minnet och på en extern cachetjänst, det var Redis. Allt var ogiltigförklarat, inställningarna specificerades i koden.

Flera servrar för backend

Baksidan av applikationen måste också skalas för att klara ökade belastningar. Det var nödvändigt att göra ett kluster från en IIS-server. Vi flyttade ansökningssession från minnet på RedisCache, vilket gjorde det möjligt att skapa flera servrar bakom en enkel lastbalanserare med round robin. Till en början användes samma Redis som för cacherna, sedan delades de upp i flera. 

Som ett resultat blev arkitekturen mer komplex...

Architectural History of Dodo IS: An Early Monolith

...men en del av spänningen var lättad.

Och sedan var det nödvändigt att göra om de laddade komponenterna, vilket är vad vi tog på oss. Vi kommer att prata om detta i nästa del.

Källa: will.com

Lägg en kommentar