Istoria arhitecturii Dodo IS: un monolit timpuriu

Sau fiecare companie nefericită cu un monolit este nefericită în felul ei.

Dezvoltarea sistemului Dodo IS a început imediat, ca și afacerea Dodo Pizza, în 2011. S-a bazat pe ideea unei digitalizări complete și totale a proceselor de afaceri și pe cont propriu, care și atunci în 2011 a stârnit o mulțime de întrebări și scepticism. Dar de 9 ani urmăm această cale - cu propria noastră dezvoltare, care a început cu un monolit.

Acest articol este un „răspuns” la întrebările „De ce să rescrieți arhitectura și să faceți schimbări atât de mari și pe termen lung?” înapoi la articolul precedent „Istoria arhitecturii Dodo IS: calea back office-ului”. Voi începe cu modul în care a început dezvoltarea Dodo IS, cum arăta arhitectura originală, cum au apărut module noi și din cauza problemelor care au trebuit să facă schimbări la scară largă.

Istoria arhitecturii Dodo IS: un monolit timpuriu

Seria de articole „Ce este Dodo IS?” spune despre:

  1. Monolit timpuriu în Dodo IS (2011-2015). (Eşti aici)

  2. Calea Back Office: baze și autobuz separate.

  3. Calea laterală client: fațadă peste bază (2016-2017). (În curs...)

  4. Istoria adevăratelor microservicii. (2018-2019). (În curs...)

  5. Terminat tăierea monolitului și stabilizarea arhitecturii. (În curs...)

Arhitectura initiala

În 2011, arhitectura Dodo IS arăta astfel:

Istoria arhitecturii Dodo IS: un monolit timpuriu

Primul modul din arhitectură este acceptarea comenzilor. Procesul de afaceri a fost:

  • clientul sună la pizzerie;

  • managerul ridică telefonul;

  • acceptă o comandă telefonică;

  • îl completează în paralel în interfața de acceptare a comenzii: ia în considerare informații despre client, date despre detaliile comenzii, adresa de livrare. 

Interfața sistemului informațional arăta cam așa...

Prima versiune din octombrie 2011:

S-a îmbunătățit ușor în ianuarie 2012

Dodo Pizza Sistem Informațional Livrare Pizza Restaurant

Resursele pentru dezvoltarea modulului de primire a primei comenzi au fost limitate. A trebuit să facem multe, rapid și cu o echipă mică. O echipă mică este formată din 2 dezvoltatori care au pus bazele întregului sistem viitor.

Prima lor decizie a determinat soarta stivei de tehnologie:

  • Backend pe ASP.NET MVC, limbaj C#. Dezvoltatorii au fost dotnetchiki, această stivă le-a fost familiară și plăcută.

  • Frontend pe Bootstrap și JQuery: interfețe de utilizator pe stiluri și scripturi auto-scrise. 

  • Baza de date MySQL: fără costuri de licență, ușor de utilizat.

  • Servere pe Windows Server, pentru că atunci .NET ar putea fi doar sub Windows (nu vom discuta despre Mono).

Fizic, toate acestea au fost exprimate în „dedic la gazdă”. 

Arhitectura aplicației de primire a comenzilor

Atunci toată lumea vorbea deja despre microservicii, iar SOA a fost folosit în proiecte mari timp de 5 ani, de exemplu, WCF a fost lansat în 2006. Dar apoi au ales o soluție fiabilă și dovedită.

Iată-l.

Istoria arhitecturii Dodo IS: un monolit timpuriu

Asp.Net MVC este Razor, care, la cererea unui formular sau de la un client, redă o pagină HTML cu randare pe server. Pe client, scripturile CSS și JS afișează deja informații și, dacă este necesar, execută cereri AJAX prin JQuery.

Solicitările de pe server ajung în clasele *Controller, unde procesarea și generarea paginii HTML finale au loc în metodă. Controlorii fac cereri către un strat de logică numit *Servicii. Fiecare dintre servicii corespundea unui anumit aspect al afacerii:

  • De exemplu, DepartmentStructureService a oferit informații despre pizzerii, despre departamente. Un departament este un grup de pizzerii condus de un singur francizat.

  • ReceivingOrdersService a acceptat și a calculat compoziția comenzii.

  • Și SMSService a trimis SMS apelând serviciile API pentru a trimite SMS-uri.

Serviciile procesează date din baza de date, logica de afaceri stocată. Fiecare serviciu avea unul sau mai multe *Arhive cu numele corespunzător. Acestea conțineau deja interogări către procedurile stocate în baza de date și un strat de cartografi. A existat o logică de afaceri în depozite, mai ales în cele care au emis date de raportare. ORM nu a fost folosit, toată lumea s-a bazat pe sql scris de mână. 

A existat, de asemenea, un strat al modelului de domeniu și clase de ajutor comune, de exemplu, clasa Order care a stocat comanda. În același loc, în strat, era un ajutor pentru conversia textului afișat în funcție de moneda selectată.

Toate acestea pot fi reprezentate printr-un astfel de model:

Istoria arhitecturii Dodo IS: un monolit timpuriu

Calea Comenzii

Luați în considerare o modalitate inițială simplificată de a crea o astfel de comandă.

Istoria arhitecturii Dodo IS: un monolit timpuriu

Inițial, site-ul era static. Avea prețuri pe el, iar deasupra - un număr de telefon și inscripția „Dacă vrei pizza - sună la numărul și comandă”. Pentru a comanda, trebuie să implementăm un flux simplu: 

  • Clientul viziteaza un site static cu preturi, selecteaza produse si suna la numarul afisat pe site.

  • Clientul numește produsele pe care dorește să le adauge la comandă.

  • Își dă adresa și numele.

  • Operatorul acceptă comanda.

  • Comanda este afișată în interfața comenzilor acceptate.

Totul începe cu afișarea meniului. Un utilizator-operator conectat acceptă o singură comandă la un moment dat. Prin urmare, coșul de tracțiune poate fi stocat în sesiunea sa (sesiunea utilizatorului este stocată în memorie). Există un obiect Coș care conține produse și informații despre clienți.

Clientul denumește produsul, operatorul dă clic pe + lângă produs și o solicitare este trimisă la server. Informațiile despre produs sunt scoase din baza de date și informațiile despre produs sunt adăugate în coș.

Istoria arhitecturii Dodo IS: un monolit timpuriu

Nota. Da, aici nu puteți extrage produsul din baza de date, ci îl puteți transfera din frontend. Dar pentru claritate, am arătat exact calea din baza de date. 

Apoi, introduceți adresa și numele clientului. 

Istoria arhitecturii Dodo IS: un monolit timpuriu

Când faceți clic pe „Creați comandă”:

  • Solicitarea este trimisă către OrderController.SaveOrder().

  • Primim Cos din sesiune, sunt produse in cantitatea de care avem nevoie.

  • Suplimentăm Coșul cu informații despre client și îl transmitem metodei AddOrder a clasei ReceivingOrderService, unde este salvat în baza de date. 

  • Baza de date are tabele cu comanda, componența comenzii, clientul și toate sunt conectate.

  • Interfața de afișare a comenzilor merge și extrage cele mai recente comenzi și le afișează.

Module noi

Preluarea comenzii a fost importantă și necesară. Nu poți face o afacere cu pizza dacă nu ai o comandă de vânzare. Prin urmare, sistemul a început să dobândească funcționalitate - aproximativ din 2012 până în 2015. În acest timp, au apărut multe blocuri diferite ale sistemului, pe care le voi apela module, spre deosebire de conceptul de serviciu sau produs. 

Un modul este un set de funcții care sunt unite de un obiectiv comun de afaceri. În același timp, sunt fizic în aceeași aplicație.

Modulele pot fi numite blocuri de sistem. De exemplu, acesta este un modul de raportare, interfețe de administrare, instrument de urmărire a alimentelor în bucătărie, autorizare. Acestea sunt toate interfețe de utilizator diferite, unele chiar au stiluri vizuale diferite. În același timp, totul se află în cadrul unei aplicații, al unui proces care rulează. 

Din punct de vedere tehnic, modulele au fost proiectate ca zonă (o astfel de idee a rămas chiar în asp.net core). Au existat fișiere separate pentru frontend, modele, precum și propriile clase de controler. Ca urmare, sistemul a fost transformat din acest...

Istoria arhitecturii Dodo IS: un monolit timpuriu

... în asta:

Istoria arhitecturii Dodo IS: un monolit timpuriu

Unele module sunt implementate de site-uri separate (proiect executabil), datorită unei funcționalități complet separate și parțial datorită unei dezvoltări puțin separate, mai concentrate. Acest:

  • teren - prima versiune site-ul dodopizza.ru.

  • Export: încărcarea rapoartelor de la Dodo IS pentru 1C. 

  • Personal - contul personal al angajatului. A fost dezvoltat separat și are propriul punct de intrare și design separat.

  • fs — un proiect pentru găzduirea statică. Mai târziu ne-am îndepărtat de el, mutând toate staticile în CDN-ul Akamai. 

Restul blocurilor au fost în aplicația BackOffice. 

Istoria arhitecturii Dodo IS: un monolit timpuriu

Explicația numelui:

  • Casier - Casier restaurant.

  • ShiftManager - interfețe pentru rolul „Shift Manager”: statistici operaționale privind vânzările la pizzerie, posibilitatea de a pune produse pe lista stop, schimbarea comenzii.

  • OfficeManager - interfețe pentru rolurile „Manager de pizzerie” și „Francizat”. Aici sunt colectate funcții pentru înființarea unei pizzerii, promoțiile sale bonus, primirea și lucrul cu angajații, rapoarte.

  • PublicScreens - interfețe pentru televizoare și tablete agățate în pizzerii. Televizoarele afișează meniuri, informații publicitare, starea comenzii la livrare. 

Au folosit un nivel comun de servicii, un bloc comun de clasă de domeniu Dodo.Core și o bază comună. Uneori, ei mai puteau conduce de-a lungul tranzițiilor unul la celălalt. Inclusiv site-uri individuale, cum ar fi dodopizza.ru sau personal.dodopizza.ru, au mers la serviciile generale.

Când au apărut module noi, am încercat să reutilizam la maximum codul de servicii deja creat, procedurile stocate și tabelele din baza de date. 

Pentru o mai bună înțelegere a dimensiunii modulelor realizate în sistem, iată o diagramă din 2012 cu planuri de dezvoltare:

Istoria arhitecturii Dodo IS: un monolit timpuriu

Până în 2015, totul era pe hartă și chiar mai mult era în producție.

  • Acceptarea comenzii a devenit un bloc separat al Centrului de contact, unde comanda este acceptată de operator.

  • Erau ecrane publice cu meniuri și informații agățate în pizzerii.

  • Bucătăria are un modul care redă automat mesajul vocal „New Pizza” atunci când sosește o nouă comandă și, de asemenea, tipărește o factură pentru curier. Acest lucru simplifică foarte mult procesele din bucătărie, permite angajaților să nu fie distrași de un număr mare de operațiuni simple.

  • Unitatea de livrare a devenit un Delivery Checkout separat, unde comanda a fost emisă curierului care a preluat anterior tura. Timpul lui de lucru a fost luat în calcul pentru calculul salariului. 

În paralel, din 2012 până în 2015, au apărut peste 10 dezvoltatori, s-au deschis 35 de pizzerii, au desfășurat sistemul în România și s-au pregătit pentru deschiderea de puncte de vânzare în Statele Unite. Dezvoltatorii nu se mai ocupau de toate sarcinile, ci erau împărțiți în echipe. fiecare specializat în propria sa parte a sistemului. 

Probleme

Inclusiv din cauza arhitecturii (dar nu numai).

Haos în bază

O bază este convenabilă. Consecvența poate fi obținută în ea și în detrimentul instrumentelor integrate în bazele de date relaționale. Lucrul cu acesta este familiar și convenabil, mai ales dacă există puține tabele și puține date.

Dar peste 4 ani de dezvoltare, baza de date s-a dovedit a avea aproximativ 600 de tabele, 1500 de proceduri stocate, dintre care multe aveau și logică. Din păcate, procedurile stocate nu aduc prea multe avantaje atunci când lucrați cu MySQL. Ele nu sunt stocate în cache de bază, iar stocarea logicii în ele complică dezvoltarea și depanarea. Reutilizarea codului este, de asemenea, dificilă.

Multe tabele nu au avut indecși potriviți, undeva, dimpotrivă, erau o mulțime de indici, ceea ce îngreuna inserarea. A fost necesar să se modifice aproximativ 20 de tabele - tranzacția pentru a crea o comandă putea dura aproximativ 3-5 secunde. 

Datele din tabele nu au fost întotdeauna în cea mai adecvată formă. Undeva era necesar să se facă denormalizare. O parte din datele primite în mod regulat erau într-o coloană sub forma unei structuri XML, ceea ce a mărit timpul de execuție, a prelungit interogările și a complicat dezvoltarea.

Pentru aceleași tabele au fost produse foarte cereri eterogene. Mesele populare au avut de suferit în special, ca tabelul menționat mai sus. comenzilor sau mese pizzerie. Au fost folosite pentru a afișa interfețe operaționale în bucătărie, analitice. Un alt site i-a contactat (dodopizza.ru), unde la un moment dat ar putea veni brusc o mulțime de cereri. 

Datele nu au fost agregate și multe calcule au avut loc din mers folosind baza. Acest lucru a creat calcule inutile și încărcare suplimentară. 

Adesea, codul a mers în baza de date când nu ar fi putut face acest lucru. Undeva nu au fost suficiente operațiuni în vrac, undeva ar fi necesar să se răspândească o cerere în mai multe prin cod pentru a accelera și a crește fiabilitatea. 

Coeziunea și ofuscarea în cod

Modulele care trebuiau să fie responsabile pentru partea lor din afacere nu au făcut-o cinstit. Unele dintre ele aveau duplicarea funcțiilor pentru roluri. De exemplu, un marketer local care este responsabil de activitatea de marketing a rețelei în orașul său a trebuit să folosească atât interfața „Admin” (pentru a crea promoții), cât și interfața „Office Manager” (pentru a vedea impactul promoțiilor asupra afacerii). Desigur, în interiorul ambelor module a folosit același serviciu care a funcționat cu promoții bonus.

Serviciile (clasele din cadrul unui proiect monolit mare) ar putea apela reciproc pentru a-și îmbogăți datele.

Cu clasele de model în sine care stochează date, munca în cod a fost efectuată diferit. Undeva existau constructori prin care se putea specifica câmpuri obligatorii. Undeva acest lucru s-a făcut prin proprietăți publice. Desigur, obținerea și transformarea datelor din baza de date a fost variată. 

Logica era fie în controlere, fie în clasele de servicii. 

Acestea par a fi probleme minore, dar au încetinit foarte mult dezvoltarea și au redus calitatea, ducând la instabilitate și erori. 

Complexitatea unei dezvoltări mari

Au apărut dificultăți în dezvoltarea în sine. A fost necesar să se facă diferite blocuri ale sistemului, și în paralel. Încadrarea nevoilor fiecărei componente într-un singur cod a devenit din ce în ce mai dificilă. Nu a fost ușor să fii de acord și să mulțumești toate componentele în același timp. La acestea s-au adăugat limitări ale tehnologiei, în special în ceea ce privește baza și interfața. A fost necesar să se abandoneze jQuery către cadrele de nivel înalt, mai ales în ceea ce privește serviciile pentru clienți (site-ul).

În unele părți ale sistemului, ar putea fi utilizate baze de date mai potrivite pentru aceasta.. De exemplu, mai târziu am avut cazul de utilizare de a trece de la Redis la CosmosDB pentru a stoca un coș de comenzi. 

Echipele și dezvoltatorii implicați în domeniul lor și-au dorit în mod clar mai multă autonomie pentru serviciile lor, atât în ​​ceea ce privește dezvoltarea, cât și lansarea. Îmbinați conflictele, eliberați probleme. Dacă pentru 5 dezvoltatori această problemă este nesemnificativă, atunci cu 10 și cu atât mai mult cu creșterea planificată, totul ar deveni mai grav. Și înainte urma să fie dezvoltarea unei aplicații mobile (a început în 2017, iar în 2018 a fost toamna mare). 

Diferite părți ale sistemului au necesitat niveluri diferite de stabilitate, dar din cauza conectivității puternice a sistemului, nu am putut oferi acest lucru. O eroare în dezvoltarea unei noi funcții în panoul de administrare ar fi putut avea loc la acceptarea unei comenzi pe site, deoarece codul este comun și reutilizabil, baza de date și datele sunt de asemenea aceleași.

Probabil că ar fi posibil să se evite aceste greșeli și probleme în cadrul unei astfel de arhitecturi monolit-modulare: faceți o împărțire a responsabilităților, refactorizați atât codul, cât și baza de date, separați clar straturile unul de celălalt, monitorizați calitatea în fiecare zi. Dar soluțiile arhitecturale alese și concentrarea pe extinderea rapidă a funcționalității sistemului au dus la probleme în ceea ce privește stabilitatea.

Cum blogul Power of the Mind a pus casele de marcat din restaurante

Dacă creșterea rețelei de pizzerii (și a încărcăturii) ar continua în același ritm, atunci după un timp scăderile ar fi astfel încât sistemul să nu se ridice. Ei bine ilustrează problemele cu care am început să ne confruntăm până în 2015, iată o astfel de poveste. 

Pe blog "Puterea mintii” a fost un widget care arăta date privind veniturile pe anul întregii rețele. Widgetul a accesat API-ul public Dodo, care furnizează aceste date. Această statistică este disponibilă în prezent la http://dodopizzastory.com/. Widgetul a fost afișat pe fiecare pagină și a făcut solicitări pe un cronometru la fiecare 20 de secunde. Solicitarea a mers pe api.dodopizza.ru și a solicitat:

  • numărul de pizzerii din rețea;

  • venitul total al rețelei de la începutul anului;

  • venituri pentru azi.

Solicitarea de statistici privind veniturile a mers direct în baza de date și a început să solicite date despre comenzi, să adunăm datele din mers și să dea suma. 

Casele de casă din restaurante au mers la același tabel de comenzi, au descărcat o listă de comenzi primite pentru astăzi și au fost adăugate noi comenzi. Casele de marcat și-au făcut solicitările la fiecare 5 secunde sau la reîmprospătarea paginii.

Diagrama arăta astfel:

Istoria arhitecturii Dodo IS: un monolit timpuriu

Într-o toamnă, Fyodor Ovchinnikov a scris un articol lung și popular pe blogul său. O mulțime de oameni au venit pe blog și au început să citească totul cu atenție. În timp ce fiecare dintre cei care au venit citea articolul, widgetul de venituri a funcționat corect și a solicitat API-ul la fiecare 20 de secunde.

API-ul a apelat la o procedură stocată pentru a calcula suma tuturor comenzilor de la începutul anului pentru toate pizzeriile din rețea. Agregarea s-a bazat pe tabelul de comenzi, care este foarte popular. Toate casele de casă ale tuturor restaurantelor deschise la acel moment merg la el. Casele de marcat nu mai răspund, comenzile nu au fost acceptate. De asemenea, nu au fost acceptați de pe site, nu au apărut pe tracker, managerul de tură nu le-a putut vedea în interfața sa. 

Aceasta nu este singura poveste. Până în toamna lui 2015, în fiecare vineri, sarcina sistemului era critică. De câteva ori am dezactivat API-ul public și, o dată, chiar a trebuit să dezactivăm site-ul, pentru că nimic nu a ajutat. A existat chiar și o listă de servicii cu ordin de oprire la sarcini grele.

De acum începe lupta noastră cu încărcăturile și pentru stabilizarea sistemului (din toamna 2015 până în toamna 2018). Atunci s-a întâmplat"toamna mare". Mai mult, uneori au apărut și eșecuri, unele erau foarte sensibile, dar perioada generală de instabilitate poate fi considerată acum trecută.

Creșterea rapidă a afacerii

De ce nu s-ar putea face imediat? Uită-te la următoarele diagrame.

Istoria arhitecturii Dodo IS: un monolit timpuriu

Tot în 2014-2015 a avut loc o deschidere în România și se pregătea o deschidere în SUA.

Rețeaua a crescut foarte repede, s-au deschis noi țări, au apărut noi formate de pizzerii, de exemplu, s-a deschis o pizzerie la food court. Toate acestea au necesitat o atenție semnificativă în special extinderii funcțiilor Dodo IS. Fara toate aceste functii, fara urmarirea in bucatarie, contabilizarea produselor si pierderilor in sistem, afisarea emiterii unei comenzi in sala food court, cu greu am fi vorbit despre arhitectura „corecta” si abordarea „corecta” a dezvoltare acum.

Un alt obstacol în calea revizuirii la timp a arhitecturii și, în general, a atenției asupra problemelor tehnice a fost criza din 2014. Lucruri de genul acesta au lovit puternic oportunitățile pentru echipe de a crește, în special pentru o afacere tânără precum Dodo Pizza.

Soluții rapide care au ajutat

Problemele au nevoie de soluții. În mod convențional, soluțiile pot fi împărțite în 2 grupe:

  • Cele rapide care sting focul și oferă o mică marjă de siguranță și ne oferă timp să ne schimbăm.

  • Sistemică și, prin urmare, lungă. Reproiectarea unui număr de module, împărțirea unei arhitecturi monolitice în servicii separate (majoritatea dintre ele nu sunt deloc micro, ci mai degrabă servicii macro și există ceva în acest sens Raportul lui Andrey Morevskiy). 

Lista uscată a schimbărilor rapide este următoarea:

Creșteți baza master

Desigur, primul lucru care se face pentru a face față sarcinilor este creșterea capacității serverului. Acest lucru a fost făcut pentru baza de date master și pentru serverele web. Din păcate, acest lucru este posibil doar până la o anumită limită, apoi devine prea scump.

Din 2014, ne-am mutat la Azure, am scris și despre acest subiect la acel moment în articolul „Cum livrează Dodo Pizza pizza folosind cloud-ul Microsoft Azure". Dar după o serie de creșteri ale serverului pentru bază, s-au confruntat cu costul. 

Replici de bază pentru citire

Au fost realizate două replici pentru bază:

ReadReplica pentru cereri de referință. Este folosit pentru a citi directoare, tip, oraș, stradă, pizzerie, produse (domeniu schimbat lent), și în acele interfețe unde este acceptabilă o mică întârziere. Au fost 2 din aceste replici, ne-am asigurat disponibilitatea lor la fel ca maestrii.

ReadReplica pentru solicitări de raportare. Această bază de date avea o disponibilitate mai mică, dar toate rapoartele au fost trimise la ea. Lăsați-i să aibă solicitări grele pentru recalculări uriașe de date, dar nu afectează baza de date principală și interfețele operaționale. 

Cache în cod

Nu existau cache-uri nicăieri în cod (deloc). Acest lucru a dus la solicitări suplimentare, nu întotdeauna necesare, către baza de date încărcată. Cache-urile au fost mai întâi atât în ​​memorie, cât și pe un serviciu cache extern, adică Redis. Totul a fost invalidat de timp, setările au fost specificate în cod.

Mai multe servere backend

Backend-ul aplicației trebuia, de asemenea, scalat pentru a face față sarcinilor de lucru crescute. A fost necesar să se facă un cluster de la un server iis. Am reprogramat sesiune de aplicare de la memorie la RedisCache, ceea ce a făcut posibilă realizarea mai multor servere în spatele unui simplu echilibrator de încărcare cu round robin. La început, a fost folosit același Redis ca pentru cache, apoi a fost împărțit în mai multe. 

Ca urmare, arhitectura a devenit mai complicată...

Istoria arhitecturii Dodo IS: un monolit timpuriu

… dar o parte din tensiune a fost eliminată.

Și apoi a fost necesar să refacem componentele încărcate, ceea ce ne-am asumat. Vom vorbi despre asta în partea următoare.

Sursa: www.habr.com

Adauga un comentariu