Storia dell'architettura Dodo IS: il monolite iniziale

Ogni sfortunata azienda con un monolite è infelice a modo suo.

Lo sviluppo del sistema Dodo IS è iniziato contemporaneamente all'attività di Dodo Pizza, nel 2011. L'idea di base era quella di una digitalizzazione totale e completa dei processi aziendali, fatta in casa, qualcosa che già nel 2011 sollevava molte domande e scetticismi. Ma ora, dopo 9 anni, continuiamo su questa strada, con uno sviluppo interno che è partito da un monolite.

Quest'articolo è una "risposta" alle domande "Perché riscrivere l'architettura e apportare modifiche così vaste e lunghe?" all'articolo precedente "Storia dell'architettura Dodo IS: il percorso del back office".Comincerò da come è iniziato lo sviluppo di Dodo IS, come era l'architettura iniziale, come sono emersi nuovi moduli e quali problemi hanno costretto a effettuare modifiche su larga scala.

Storia dell'architettura Dodo IS: il monolite iniziale

La serie di articoli «Cos'è Dodo IS?» parlerà di:

  1. Il primo monolite in Dodo IS (2011-2015). (You are here)

  2. Il percorso del back office: database separati e bus.

  3. Il percorso della parte client: facciata sopra il database (2016-2017). (In progress…)

  4. Storia dei veri microservizi. (2018-2019). (In progress…)

  5. La conclusione della suddivisione del monolite e la stabilizzazione dell'architettura. (In progress…)

Architettura iniziale

Nel 2011, l'architettura di Dodo IS appariva così:

Storia dell'architettura Dodo IS: il monolite iniziale

Il primo modulo nell'architettura è l'accettazione degli ordini. Il processo aziendale era il seguente:

  • il cliente chiama la pizzeria;

  • il manager risponde al telefono;

  • prende l'ordine per telefono;

  • si occupa parallelamente di raccogliere le informazioni sull’ordine nell'interfaccia di ricezione degli ordini: vengono considerati i dati del cliente, le informazioni sui dettagli dell'ordine, e l'indirizzo di consegna. 

L'interfaccia del sistema informativo appariva all'incirca così…

Prima versione di ottobre 2011:

Leggermente migliorata a gennaio 2012

Sistema informativo per il ristorante di pizza Dodo Pizza Delivery

Le risorse per lo sviluppo del primo modulo di ricezione degli ordini erano limitate. Bisognava fare molte cose, rapidamente e con pochi membri. Un piccolo team – composto da 2 sviluppatori – che ha posto le basi per l'intero sistema futuro.

La loro prima soluzione ha determinato il futuro stack tecnologico:

  • Backend in ASP.NET MVC, linguaggio C#. Gli sviluppatori erano familiarizzati con .NET, quindi questo stack era loro noto e gradito.

  • Frontend in Bootstrap e JQuery: interfacce utente con stili e script personalizzati. 

  • Database MySQL: senza costi di licenza, facile da usare.

  • Server su Windows Server, poiché .NET all'epoca funzionava solo su Windows (Non discuteremo di Mono).

Fisicamente, tutto ciò si traduceva in un 'dedicato dal provider'. 

Architettura dell'applicazione per la ricezione degli ordini

A quel punto si parlava già di microservizi, mentre l'SOA era utilizzato da circa 5 anni nei grandi progetti; ad esempio, WCF è stato rilasciato nel 2006. Ma allora si scelse una soluzione affidabile e collaudata.

Ecco.

Storia dell'architettura Dodo IS: il monolite iniziale

Asp.Net MVC è Razor, che restituisce una pagina HTML con rendering sul server su richiesta da un modulo o da un client. Sul client, CSS e script JS visualizzano le informazioni e, se necessario, eseguono richieste AJAX tramite JQuery.

Le richieste al server vengono gestite nelle classi *Controller, dove si svolge l'elaborazione e la generazione della pagina HTML finale. I controller fanno richieste al livello di logica, chiamato *Services. Ognuno di questi servizi risponde a uno specifico aspetto del business:

  • Ad esempio, il DepartmentStructureService forniva informazioni sulle pizzerie e sui dipartimenti. Un dipartimento è un gruppo di pizzerie sotto la gestione di un franchisee.

  • Il ReceivingOrdersService accettava e calcolava la composizione dell'ordine.

  • E il SmsService inviava messaggi SMS, chiamando i servizi API per l'invio degli SMS.

I servizi elaboravano i dati dal database, gestivano la logica aziendale. Ogni servizio aveva uno o più *Repository con un nome corrispondente. In questi erano già presenti le query per le stored procedure nel database e il livello dei mapper. Nelle stored procedure c'era la logica aziendale, soprattutto in quelle che restituivano dati di report. Non è stato utilizzato ORM, tutti si affidavano a SQL scritto a mano. 

C'era anche uno strato di modello di dominio e classi helper generali, ad esempio, la classe Order, che conservava l'ordine. Lì, nello strato, si trovava anche un helper per la conversione del testo di visualizzazione in base alla valuta selezionata.

Tutto questo può essere rappresentato da un modello:

Storia dell'architettura Dodo IS: il monolite iniziale

Il percorso dell'ordine

Consideriamo un percorso iniziale semplificato per la creazione di un tale ordine.

Storia dell'architettura Dodo IS: il monolite iniziale

Inizialmente, il sito era statico. Conteneva i prezzi, con un numero di telefono e la scritta 'Vuoi una pizza? Chiama il numero e ordina' in cima. Per effettuare un ordine, dobbiamo implementare un semplice flusso: 

  • Il cliente visita il sito statico con i prezzi, seleziona i prodotti e chiama il numero indicato sul sito.

  • Il cliente elenca i prodotti che desidera aggiungere all'ordine.

  • Fornisce il proprio indirizzo e nome.

  • L'operatore accetta l'ordine.

  • L'ordine viene visualizzato nell'interfaccia degli ordini ricevuti.

Tutto inizia con la visualizzazione del menu. Un utente-operator può accettare un solo ordine alla volta. Pertanto, il carrello draft può essere memorizzato nella sua sessione (la sessione dell'utente è conservata in memoria). Qui troviamo un oggetto Cart, in cui sono presenti i prodotti e le informazioni sul cliente.

Il cliente nomina il prodotto, l'operatore clicca su + accanto al prodotto, e viene inviata una richiesta al server. Le informazioni sul prodotto vengono recuperate dal database e aggiunte al carrello.

Storia dell'architettura Dodo IS: il monolite iniziale

Nota. Sì, qui non è necessario estrarre il prodotto dal database, ma si possono inviare le informazioni direttamente dal frontend. Tuttavia, per chiarezza, ho mostrato il percorso dal database. 

Dopo introduciamo l'indirizzo e il nome del cliente. 

Storia dell'architettura Dodo IS: il monolite iniziale

Quando si clicca su "Crea ordine":

  • Inviare la richiesta a OrderController.SaveOrder().

  • Otteniamo il carrello dalla sessione, dove ci sono i prodotti nella quantità necessaria.

  • Completiamo il carrello con le informazioni sul cliente e lo passiamo al metodo AddOrder della classe ReceivingOrderService, dove viene salvato nel database. 

  • Nel database ci sono tabelle con l'ordine, il contenuto dell'ordine e il cliente, tutte collegate tra loro.

  • L'interfaccia di visualizzazione dell'ordine persegue e recupera gli ultimi ordini e li riflette.

Nuovi moduli

La ricezione degli ordini è stata fondamentale e necessaria. Non si può avviare un business di vendita di pizza senza un sistema per la gestione degli ordini. Così, dal 2012 al 2015, il sistema ha cominciato ad arricchirsi di funzionalità. Durante questo periodo, sono emersi molti diversi segmenti del sistema che io chiamerò moduli, in contrapposizione al concetto di servizio o prodotto. 

Un modulo è un insieme di funzioni unite da un obiettivo commerciale comune. Fisicamente, si trovano all'interno di un'unica applicazione.

I moduli possono essere considerati come dei blocchi di sistema. Ad esempio, ci sono il modulo report, le interfacce dell'admin panel, il tracker dei prodotti in cucina, e l'autenticazione. Questi rappresentano interfacce diverse per l'utente, alcune delle quali hanno persino stili visivi differenti. Tuttavia, sono tutte parte di una stessa applicazione, di un unico processo operativo. 

Tecnicamente, i moduli erano strutturati come Area (quest'idea è rimasta anche in asp.net core). C'erano file separati per il frontend, i modelli, e anche le proprie classi di controller. Alla fine, il sistema si è trasformato da così...

Storia dell'architettura Dodo IS: il monolite iniziale

…a così:

Storia dell'architettura Dodo IS: il monolite iniziale

Alcuni moduli sono implementati da siti web separati (progetto eseguibile) a causa di funzionalità completamente distinte e parzialmente a causa di uno sviluppo più mirato. Questi sono:

  • Sitola prima versione del sito dodopizza.ru.

  • Esportazione: esportazione di report da Dodo IS per 1C. 

  • Personal — l'area personale dell'impiegato. Sviluppato separatamente, ha il proprio punto di accesso e un design unico.

  • fs — progetto per l'hosting della staticità. Successivamente, ci siamo allontanati da esso, trasferendo tutta la staticità su CDN Akamai. 

Altri blocchi si trovavano nell'app BackOffice. 

Storia dell'architettura Dodo IS: il monolite iniziale

Spiegazione sui nomi:

  • Cashier — Cassa del ristorante.

  • ShiftManager — interfacce per il ruolo di "Manager di turno": statistiche operative sulle vendite della pizzeria, possibilità di mettere prodotti nella lista nera, modificare ordini.

  • OfficeManager — interfacce per il ruolo di "Gestore della pizzeria" e "Franchising". Qui sono raccolte le funzioni per impostare la pizzeria, le sue promozioni bonus, gestire e lavorare con i dipendenti, report.

  • PublicScreens — interfacce per televisori e tablet, appesi nelle pizzerie. Nei televisori vengono visualizzati il menu, le informazioni pubblicitarie, lo stato dell'ordine al momento dell'emissione. 

Hanno utilizzato uno strato di servizi condivisi, un blocco comune di classi di dominio Dodo.Core, oltre a un database condiviso. A volte potevano anche comunicare tra loro. Compresi i servizi comuni, anche i singoli siti, come dodopizza.ru o personal.dodopizza.ru, vi accedevano.

Quando emergevano nuovi moduli, cercavano di riutilizzare il codice già creato per i servizi, le procedure memorizzate e le tabelle nel database il più possibile. 

Per una migliore comprensione dell'entità dei moduli realizzati nel sistema, ecco uno schema del 2012 con i piani di sviluppo:

Storia dell'architettura Dodo IS: il monolite iniziale

Entro il 2015, tutto nello schema e anche di più era in produzione.

  • La ricezione degli ordini è diventata un blocco separato del Centro Contatti, dove l'ordine viene preso dall'operatore.

  • Sono apparsi schermi pubblici con menù e informazioni, appesi nelle pizzerie.

  • In cucina c'è un modulo che riproduce automaticamente un messaggio vocale "Nuova pizza" quando arriva un nuovo ordine e stampa la bolla di consegna per il corriere. Questo semplifica notevolmente i processi in cucina, consentendo ai dipendenti di non distrarsi con un gran numero di operazioni semplici.

  • Il blocco delle consegne è diventato un'area separata di Cassa Consegna, dove l'ordine veniva fornito al corriere, il quale si era precedentemente presentato. Il suo orario di lavoro veniva considerato per il calcolo della retribuzione. 

Parallelamente, dal 2012 al 2015 sono emersi oltre 10 sviluppatori, sono state aperte 35 pizzerie, il sistema è stato implementato in Romania e sono state preparate le aperture negli Stati Uniti. Gli sviluppatori non si occupavano più di tutti i compiti, ma erano divisi in team, ognuno specializzato nella propria parte del sistema. 

Problemi

Questo anche a causa dell'architettura (ma non solo).

Caos nel database

Un unico database è comodo. Permette di raggiungere la coerenza, grazie agli strumenti integrati nei database relazionali. Lavorare con esso è familiare e conveniente, soprattutto se ci sono poche tabelle e pochi dati.

Tuttavia, dopo 4 anni di sviluppo, nel database erano presenti circa 600 tabelle e 1500 procedure memorizzate, molte delle quali contenevano anche logica. Purtroppo, le procedure memorizzate non offrono vantaggi significativi quando si lavora con MySQL. Non vengono memorizzate nella cache dal database e l'inserimento di logica al loro interno complica lo sviluppo e il debug. Anche il riutilizzo del codice è difficoltoso.

Molte tabelle non avevano indici appropriati., a volte, al contrario, c'erano moltissimi indici, il che rendeva difficile l'inserimento. Era necessario modificare circa 20 tabelle — la transazione per la creazione di un ordine poteva richiedere circa 3-5 secondi. 

I dati nelle tabelle non erano sempre nella forma più adeguata.. In alcuni casi era necessario effettuare una denormalizzazione. Parte dei dati ottenuti regolarmente era in una colonna sotto forma di struttura XML, il che aumentava i tempi di esecuzione, allungava le query e complicava lo sviluppo.

Alle stesse tabelle venivano effettuate molte richieste eterogenee.. Soprattutto le tabelle più popolari, come quella menzionata orders o la tabella pizzeria. Venivano utilizzate per visualizzare interfacce operative in cucina, per analisi. Inoltre, il sitododopizza.ru, poteva ricevere improvvisamente molte richieste in qualsiasi momento. 

I dati non erano aggregati e molti calcoli avvenivano on-the-fly tramite il database. Questo creava calcoli superflui e un carico aggiuntivo. 

Spesso il codice si collegava al database quando poteva evitare di farlo. A volte mancavano operazioni bulk, altre volte era necessario suddividere una richiesta in più attraverso il codice per accelerare e aumentare l'affidabilità. 

Coerenza e complessità nel codice

I moduli che avrebbero dovuto occuparsi della propria area di business non lo facevano in modo onesto. Alcuni di essi avevano duplicazioni nelle funzionalità per i ruoli. Ad esempio, un marketer locale responsabile dell'attività di marketing della rete nella propria città doveva utilizzare sia l'interfaccia 'Admin' (per creare promozioni) sia l'interfaccia 'Office Manager' (per visualizzare l'impatto delle promozioni sul business). Naturalmente, internamente entrambi i moduli utilizzavano lo stesso servizio che gestiva le promozioni bonificate.

I servizi (classi all'interno di un unico grande progetto monolitico) potevano chiamarsi a vicenda per arricchire i propri dati.

Con le stesse classi-modello che memorizzano i dati, il lavoro nel codice procedeva in modi diversi. C'erano dei costruttori attraverso i quali si potevano specificare i campi obbligatori. In altri casi, ciò avveniva tramite proprietà pubbliche. Certamente, l'acquisizione e la trasformazione dei dati dal database erano variabili. 

La logica era nei controller o nelle classi dei servizi. 

Questi possono sembrare problemi insignificanti, ma rallentavano notevolmente lo sviluppo e riducevano la qualità, portando a instabilità e errori. 

La complessità dello sviluppo su larga scala

Le difficoltà si sono presentate anche nella stessa fase di sviluppo. Era necessario realizzare diversi blocchi del sistema, e farlo in parallelo. Soddisfare le esigenze di ogni componente in un unico codice diventava sempre più difficile. Non era semplice raggiungere un accordo e soddisfare tutti i componenti contemporaneamente. A ciò si aggiungevano le limitazioni tecnologiche, in particolare riguardo al backend e al frontend. Era necessario rinunciare a jQuery in favore di framework di alto livello, soprattutto per quanto riguarda i servizi client (sito web).

In alcune parti del sistema potrebbero essere utilizzati database più adatti a questo scopo. Ad esempio, successivamente abbiamo avuto un caso di transizione da Redis a CosmosDB per la memorizzazione del carrello degli ordini. 

I team e gli sviluppatori che lavorano nel proprio campo desideravano chiaramente maggiore autonomia per i propri servizi, sia per quanto riguarda lo sviluppo che il deployment. I conflitti durante il merge e i problemi durante i rilasci. Se per 5 sviluppatori questo problema è trascurabile, con 10, e soprattutto con una crescita prevista, tutto sarebbe diventato più serio. Inoltre, si prevedeva lo sviluppo di un'app mobile (iniziato nel 2017, e nel 2018 c'è stato un grande crollo). 

Diverse parti del sistema richiedevano diversi indicatori di stabilità, ma a causa della forte interconnessione del sistema, non potevamo garantirlo. Un errore nello sviluppo di una nuova funzionalità nell'interfaccia di amministrazione poteva facilmente influire sulla ricezione degli ordini sul sito, dato che il codice è comune e riutilizzabile, così come il database e i dati.

Probabilmente, all'interno di un'architettura monolitica-modulare si sarebbero potuti evitare questi errori e problemi: separare le responsabilità, eseguire il refactoring sia del codice che del database, mantenere una chiara separazione dei layer e controllare la qualità ogni giorno. Tuttavia, le scelte architettoniche adottate e il focus sull'espansione rapida delle funzionalità del sistema hanno portato a problemi di stabilità.

Come il blog Sila u ma ha incaricato le casse nei ristoranti

Se la crescita della rete di pizzerie (e il carico) fosse continuata allo stesso ritmo, dopo un po' ci sarebbero stati dei crolli tali che il sistema non sarebbe più stato in grado di riprendersi. Una storia come questa illustra bene i problemi con cui abbiamo iniziato a confrontarci verso il 2015. 

Nel blog “Sila u ma” c'era un widget che mostrava i dati sulle entrate annuali dell'intera rete. Il widget si collegava a un'API pubblica di Dodo, che fornisce questi dati. Attualmente, queste statistiche sono disponibili su http://dodopizzastory.com/. Il widget veniva visualizzato su ogni pagina e effettuava richieste ogni 20 secondi. La richiesta andava su api.dodopizza.ru e richiedeva:

  • il numero di pizzerie nella rete;

  • il totale delle entrate della rete dall'inizio dell'anno;

  • le entrate di oggi.

La richiesta di statistiche sui ricavi veniva inviata direttamente al database, iniziando a raccogliere i dati sugli ordini, aggregando i dati in tempo reale e restituendo il totale. 

Nella stessa tabella degli ordini venivano caricati i registratori di cassa nei ristoranti, scaricando l'elenco degli ordini accettati per oggi, e venivano aggiunti nuovi ordini. I registratori effettuavano le loro richieste ogni 5 secondi o al momento dell'aggiornamento della pagina.

Lo schema era il seguente:

Storia dell'architettura Dodo IS: il monolite iniziale

Un giorno d'autunno, Fëdor Ovčinnikov scrisse un lungo e popolare articolo sul suo blog. Il blog ricevette molte visite e le persone iniziarono a leggere attentamente. Mentre ogni persona presente leggevano l'articolo, il widget dei ricavi funzionava correttamente, richiedendo l'API ogni 20 secondi.

L'API chiamava una procedura memorizzata per calcolare il totale di tutti gli ordini dall'inizio dell'anno per tutte le pizzerie della rete. L'aggregazione avveniva sulla tabella orders, molto popolare. Anche tutti i registratori di cassa dei ristoranti aperti in quel momento accedevano a essa. I registratori smisero di rispondere, gli ordini non venivano accettati. Inoltre, non erano accettati dal sito, non apparivano nel tracker, e il manager del turno non poteva vederli nella sua interfaccia. 

Questa non è l'unica storia. Entro l'autunno del 2015, il carico sul sistema era critico ogni venerdì. Diverse volte abbiamo dovuto disattivare l'API pubblica e un giorno, siamo stati costretti a spegnere il sito perché nulla sembrava funzionare. Esisteva anche una lista di servizi con l'ordine di disattivazione in caso di carichi gravi.

Da questo momento inizia la nostra battaglia contro i carichi e per la stabilizzazione del sistema (dalla primavera 2015 all'autunno 2018). È proprio in quel periodo che si è verificato "Il Grande Crollo". Anche in seguito ci sono stati alcuni guasti, alcuni dei quali erano piuttosto critici, ma ora possiamo considerare che il periodo generale di instabilità sia superato.

Crescita rapida del business

Perché non si poteva "fare tutto bene fin dall'inizio"? È sufficiente dare un'occhiata ai seguenti grafici.

Storia dell'architettura Dodo IS: il monolite iniziale

Nel 2014-2015 c'è stata anche un'apertura in Romania e si stava preparando l'apertura negli Stati Uniti.

La rete è cresciuta molto rapidamente, nuove nazioni si sono aperte, sono emersi nuovi formati di pizzerie, ad esempio, è stata aperta una pizzeria nel food court. Tutto questo ha richiesto una grande attenzione all'espansione delle funzionalità di Dodo IS. Senza tutte queste funzionalità, senza il tracciamento in cucina, la gestione delle scorte e delle perdite nel sistema, e la visualizzazione degli ordini nella sala del food court, difficilmente staremmo discutendo di una «corretta» architettura e di un «approccio giusto» nello sviluppo.

Un altro ostacolo alla revisione tempestiva dell'architettura e alla considerazione dei problemi tecnici è stato la crisi del 2014. Tali eventi colpiscono duramente le possibilità di crescita dei team, soprattutto per una giovane azienda come Dodo Pizza.

Soluzioni rapide che hanno aiutato

I problemi richiedevano soluzioni. Condizionatamente, le soluzioni possono essere suddivise in 2 gruppi:

  • Rapide, che spengono il fuoco e forniscono un piccolo margine di sicurezza, guadagnando tempo per le modifiche.

  • Sistemiche e, quindi, più lunghe. Il re-engineering di vari moduli, la separazione dell'architettura monolitica in servizi separati (la maggior parte di essi sono piuttosto macro-servizi piuttosto che micro-servizi, e su questo c'è la relazione di Andrey Morevsky). 

L'elenco asciutto delle modifiche è il seguente:

Master della scalabilità

Naturalmente, la prima azione intrapresa per affrontare i carichi di lavoro è stata quella di aumentare la potenza del server. Questo è stato fatto per il master e per i server web. Purtroppo, ciò è possibile solo fino a un certo limite, oltre il quale diventa troppo costoso.

Dal 2014 siamo passati ad Azure, su questo tema abbiamo scritto anche all'epoca nell'articolo «Come Dodo Pizza consegna la pizza utilizzando il cloud di Microsoft Azure». Tuttavia, dopo una serie di aumenti del server per il database, ci siamo trovati a dover affrontare i costi. 

Repliche del database per la lettura

Abbiamo creato due repliche per il database:

ReadReplica per le richieste di riferimento. Viene utilizzata per la lettura di riferimento, come città, strade, pizzerie, prodotti (dominio lentamente cambiato), e in quelle interfacce dove è consentito un leggero ritardo. Queste repliche erano due, assicuravamo la loro disponibilità proprio come quella del master.

ReadReplica per le richieste di report. Questa replica aveva una disponibilità inferiore, ma tutti i report vi accedevano. Anche se le loro richieste sono pesanti per enormi rielaborazioni dei dati, non influenzano il database principale e le interfacce operative. 

Cache nel codice

Non c'erano cache nel codice (affatto). Questo portava a richieste aggiuntive, non sempre necessarie, a un database sovraccarico. Le cache erano inizialmente sia in memoria che in un servizio di cache esterno, che era Redis. Tutto veniva invalidato nel tempo, le impostazioni erano definite nel codice.

Diversi server per il backend

Anche il backend dell'applicazione doveva essere scalato per sopportare carichi maggiori. Era necessario creare un cluster da un singolo server IIS. Abbiamo migrato la sessione delle applicazioni da memoria a RedisCache, il che ha permesso di avere diversi server dietro un semplice bilanciatore di carico con round robin. Inizialmente veniva utilizzato lo stesso Redis usato per le cache, poi è stato distribuito su più server. 

Di conseguenza, l'architettura si è complicata…

Storia dell'architettura Dodo IS: il monolite iniziale

…ma parte della tensione è stata alleviata.

E poi era necessario rifare i componenti sovraccarichi, e questo è ciò su cui ci siamo messi al lavoro. Ne parleremo nella prossima parte.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster