Ciao a tutti! Abbiamo ottime notizie, a giugno OTUS riporta il corso , per cui tradizionalmente condividiamo con voi materiale utile.

Se ti sei trovato di fronte a tutta questa storia dei microservizi senza alcun contesto, è comprensibile considerarla un po' strana. Suddividere un'applicazione in frammenti connessi tramite rete implica necessariamente l'aggiunta di modalità complesse di resilienza ai guasti nel sistema distribuito risultante.
Nonostante questo approccio preveda la suddivisione in molteplici servizi indipendenti, l'obiettivo finale è molto più grande della semplice operatività di questi servizi su macchine diverse. Qui si parla di interazione con l'ambiente circostante, che per sua natura è anch'esso distribuito. Non in un senso tecnico, ma piuttosto in termini di ecosistema, composto da molte persone, team, programmi, e ognuna di queste parti deve in qualche modo svolgere il proprio lavoro.
Le aziende, ad esempio, rappresentano un insieme di sistemi distribuiti che insieme contribuiscono al raggiungimento di un determinato obiettivo. Abbiamo ignorato questo fatto per decenni, cercando di ottenere un'unificazione, trasferendo file tramite FTP o utilizzando strumenti di integrazione aziendale, concentrandoci sulle nostre singole ambizioni isolate. Ma con l'arrivo dei servizi, tutto è cambiato. I servizi ci hanno aiutato a guardare oltre l'orizzonte e a vedere un mondo di programmi interdipendenti che lavorano insieme. Tuttavia, per lavorare con successo, è necessario riconoscere e progettare due mondi fondamentalmente diversi: il mondo esterno, in cui viviamo in un ecosistema di molti altri servizi, e il nostro mondo personale, interno, dove regniamo da soli.

Questo mondo distribuito è diverso da quello in cui siamo cresciuti e a cui siamo abituati. I principi di costruzione dell'architettura monolitica tradizionale non reggono a nessuna critica. Pertanto, una corretta comprensione di tali sistemi è qualcosa di più che creare un bel diagramma su una lavagna bianca o una fantastica prova di concetto. Si tratta di garantire che un tale sistema funzioni con successo nel lungo periodo. Fortunatamente, i servizi esistono da abbastanza tempo, anche se si presentano in forme diverse. sono ancora rilevanti, anche se conditi da Docker, Kubernetes e leggermente trasandati da barbe hipster.
Quindi, oggi daremo un'occhiata a come sono cambiate le regole, perché dobbiamo ripensare il nostro approccio ai servizi e ai dati che si scambiano e perché avremo bisogno di un set di strumenti completamente diverso.
L'incapsulamento non sarà sempre un tuo amico
I microservizi possono funzionare in modo indipendente l'uno dall'altro. Questa caratteristica è ciò che conferisce loro il maggior valore. Questa stessa caratteristica consente ai servizi di scalare e crescere. Non tanto in termini di scalabilità fino a quadrilioni di utenti o petabyte di dati (anche se possono aiutare in questo), quanto in termini di scalabilità dal punto di vista umano, poiché team e organizzazioni crescono continuamente.

Tuttavia, l'indipendenza è una lama a doppio taglio. Il che significa che un servizio può funzionare facilmente e senza stress. Ma se all'interno di un servizio viene implementata una funzione che richiede l'utilizzo di un altro servizio, alla fine dobbiamo apportare modifiche a entrambi i servizi quasi simultaneamente. In un monolite, è facile farlo, basta apportare la modifica e inviarla in produzione, ma nel caso della sincronizzazione di servizi indipendenti ci saranno più problemi. Il coordinamento tra team e cicli di rilascio distrugge la flessibilità.

Nel contesto dell'approccio standard, si cerca semplicemente di evitare cambiamenti problematici, suddividendo chiaramente le funzionalità tra i servizi. Un servizio di ingresso unico nel sistema può essere un buon esempio qui. Ha un ruolo ben definito che lo differenzia dagli altri servizi. Tale chiara suddivisione significa che, in un mondo di requisiti in rapida evoluzione per i servizi circostanti, il servizio di ingresso unico nel sistema difficilmente cambierà. Esiste all'interno di un contesto rigidamente limitato.

Il problema è che nel mondo reale i servizi aziendali non possono mantenere una separazione netta dei ruoli. Ad esempio, gli stessi servizi aziendali spesso lavorano con dati provenienti da altri servizi simili. Se ti occupi di vendita al dettaglio online, l'elaborazione del flusso degli ordini, del catalogo dei prodotti o delle informazioni sugli utenti diventa una necessità per molti dei tuoi servizi. Ogni servizio avrà bisogno di accesso a questi dati per funzionare.

La maggior parte dei servizi aziendali utilizza lo stesso flusso di dati, quindi il loro funzionamento è inestricabilmente intrecciato.
Così siamo giunti a un punto importante di cui vale la pena parlare. Mentre i servizi funzionano bene per componenti dell'infrastruttura che operano in gran parte in modo isolato, la maggior parte dei servizi aziendali risulta essere molto più interconnessa.
Dichotomia dei dati
Forse esistono già approcci orientati ai servizi, ma c'è ancora poca informazione su come scambiare grandi volumi di dati tra i servizi.
Il problema principale è che dati e servizi sono inseparabili. Da un lato, l'incapsulamento ci invita a nascondere i dati, in modo che i servizi possano essere separati l'uno dall'altro e facilitare la loro crescita e le future modifiche. D'altra parte, abbiamo bisogno di poter condividere e gestire liberamente i dati comuni, come qualsiasi altro dato. Si tratta di avere la possibilità di iniziare a lavorare immediatamente, altrettanto liberamente che in qualsiasi altro sistema informativo.
Tuttavia, i sistemi informativi hanno poco a che fare con l'incapsulamento. In realtà, è quasi l'opposto. I database fanno tutto il possibile per consentire l'accesso ai dati in essi conservati. Sono dotati di un potente interfaccia dichiarativa che ti permette di modificare i dati come desideri. Questa funzionalità è importante nella fase di ricerca preliminare, ma non per la gestione della crescente complessità di un servizio in continua evoluzione.

E qui sorge il dilemma. Il paradosso. La dichotomia. Infatti, i sistemi informativi riguardano la fornitura di dati, mentre i servizi riguardano la loro occultazione.
Queste due forze sono fondamentali. Esse costituiscono la base della maggior parte del nostro lavoro, combattendo costantemente per il predominio nei sistemi che creiamo.
Con la crescita e l'evoluzione dei sistemi di servizi, vediamo diverse manifestazioni delle conseguenze della diade dei dati. O l'interfaccia del servizio cresce, offrendo un insieme di funzioni sempre più ampio e inizia a sembrare un database artigianale molto strano, oppure ci imbattiamo in una delusione e implementiamo qualche modo per estrarre o trasferire massivamente interi dataset da un servizio all'altro.

A sua volta, la creazione di qualcosa che appare come un database artigianale porterà a una serie di problemi. Non scenderemo nei dettagli di quanto sia pericoloso shared database, semplicemente diremo che rappresenta significative e costose difficoltà ingegneristiche e operative Peggio, i volumi di dati amplificano i problemi ai confini dei servizi. Più dati condivisi ci sono all'interno del servizio, più complessa diventa l'interfaccia e più difficile sarà integrare i set di dati provenienti da diversi servizi.
Un approccio alternativo con estrazione e trasferimento di interi set di dati ha anch'esso i suoi problemi. L'approccio comune a questo problema appare come una semplice estrazione e archiviazione di un set di dati nella sua interezza, e poi l'archiviazione locale in ogni servizio utilizzatore.
Il problema è che diversi servizi interpretano in modo diverso i dati che consumano. Questi dati sono sempre a portata di mano. Vengono modificati e trattati in locale. Abbastanza rapidamente smettono di avere qualsiasi cosa in comune con i dati alla fonte.

Più le copie sono mutabili, più i dati si discosteranno nel tempo.

Peggio ancora, tali dati sono difficili da correggere in retrospettiva (
MDM Per trovare una soluzione a questo problema riguardo ai dati condivisi, è necessario pensare diversamente. Devono diventare oggetti di prima classe nelle architetture che costruiamo.
Pat Helland chiamano questi dati "esterni", e questa è una caratteristica molto importante. Abbiamo bisogno di incapsulamento per non rivelare la struttura interna del servizio, ma dobbiamo facilitare l'accesso dei servizi ai dati condivisi, affinché possano svolgere correttamente il loro lavoro.

Il problema è che nessuno degli approcci attuali è più rilevante, poiché né le interfacce di servizio, né lo scambio di messaggi, né il Shared Database offrono una buona soluzione per lavorare con i dati esterni. Le interfacce di servizio non sono adatte per lo scambio di dati su alcuna scala. Lo scambio di messaggi sposta i dati, ma non ne conserva la storia, quindi col passare del tempo i dati vengono danneggiati. I Shared Database si concentrano troppo su un singolo punto, bloccando così lo sviluppo del progresso. Rimanere bloccati in un ciclo di inefficacia dei dati è inevitabile:

Ciclo di inefficacia dei dati
Flussi: un approccio decentralizzato ai dati e ai servizi
Idealmente, dobbiamo cambiare l'approccio su come i servizi lavorano con i dati condivisi. Attualmente, qualsiasi approccio si scontra con la dicotomia sopra menzionata, poiché non esiste una polvere magica che possa essere sparsa generosamente per farla scomparire. Tuttavia, possiamo ripensare al problema e giungere a un compromesso.
Questo compromesso implica un certo grado di centralizzazione. Possiamo sfruttare il meccanismo dei log distribuiti, poiché esso fornisce flussi affidabili e scalabili. Ora è necessario che i servizi possano connettersi e lavorare con questi flussi condivisi, tuttavia vogliamo evitare complessi "God Service" centralizzati che svolgono tale elaborazione. Quindi, la soluzione migliore è incorporare l'elaborazione dei flussi in ogni servizio consumatore. In questo modo, i servizi potranno unire set di dati provenienti da diverse fonti e lavorare con essi come necessario.
Uno dei modi per raggiungere un approccio simile è utilizzare una piattaforma di streaming. Ci sono molte opzioni, ma oggi ci concentreremo su Kafka, poiché l'uso della sua Stateful Stream Processing consente di affrontare in modo efficace il problema presentato.

L'uso del meccanismo di logging distribuito ci permette di seguire il sentiero già tracciato e di utilizzare lo scambio di messaggi per lavorare con . Si ritiene che questo approccio consenta una migliore scalabilità e separazione rispetto al meccanismo "richiesta-risposta", poiché restituisce il controllo del flusso al destinatario e non al mittente. Tuttavia, per ogni cosa in questa vita si deve pagare, e qui avrai bisogno di un broker. Ma per i grandi sistemi questo compromesso ne vale la pena (cosa che non si può dire delle tue applicazioni web medie).
Se la registrazione distribuita è gestita da un broker e non da un tradizionale sistema di messaggistica, puoi sfruttare funzionalità aggiuntive. Il trasporto può essere scalato linearmente quasi altrettanto bene quanto un sistema di file distribuito. I dati possono essere conservati nei log per un periodo di tempo sufficientemente lungo, quindi otteniamo non solo uno scambio di messaggi, ma anche un archivio di informazioni. Un archivio scalabile senza paura di ottenere uno stato condiviso modificabile.
Successivamente, è possibile utilizzare un meccanismo di stateful stream processing (elaborazione del flusso con stato) per aggiungere strumenti dichiarativi di database ai servizi consumatori. Questo è un concetto molto importante. Finché i dati vengono conservati in flussi condivisi, a cui possono accedere tutti i servizi, la fusione e l'elaborazione effettuate dal servizio sono private. Si trovano isolate all'interno di un contesto rigorosamente limitato.

Elimina la dicotomia dei dati separando il flusso di stati immutabili. Poi aggiungi questa funzione a ciascun servizio tramite Stateful Stream Processing.
Quindi, se il tuo servizio deve gestire ordini, catalogo prodotti e magazzino, avrà accesso completo: sei tu a decidere quali dati unire, dove elaborarli e come dovrebbero cambiare nel tempo. Nonostante i dati siano condivisi, la gestione di essi è completamente decentralizzata. Avviene all'interno di ciascun servizio, in un mondo in cui tutto avviene secondo le tue regole.

Condividi i dati in modo da non compromettere la loro integrità. Incapsula la funzione, non la fonte, in ciascun servizio di cui ha bisogno.
Capita che i dati debbano essere spostati massicciamente. A volte il servizio richiede un insieme storico locale di dati nel motore di database scelto. La questione è che si può garantire che, se necessario, una copia può essere ripristinata dalla fonte tramite il meccanismo di logging distribuito. I connettori in Kafka gestiscono ottimamente questo compito.
Pertanto, l'approccio esaminato oggi presenta diversi vantaggi:
- I dati vengono utilizzati sotto forma di flussi comuni, che possono essere memorizzati a lungo nei log, mentre il meccanismo di lavoro con i dati condivisi è incorporato in ciascun contesto specifico, il che consente ai servizi di funzionare in modo semplice e veloce. In questo modo si può equilibrare la dicotomia dei dati.
- I dati provenienti da vari servizi possono essere facilmente combinati in insiemi. Questo semplifica l'interazione con i dati comuni e elimina la necessità di mantenere insiemi locali di dati nel database.
- Lo Stateful Stream Processing memorizza solo i dati, mentre la fonte della verità rimangono i log comuni, quindi il problema della corruzione dei dati nel tempo non è così pressante.
- In sostanza, i servizi sono guidati dai dati, quindi nonostante la continua crescita dei volumi di dati, i servizi possono ancora rispondere rapidamente agli eventi aziendali.
- I problemi di scalabilità ricadono sul broker, non sui servizi. Questo riduce notevolmente la complessità nella scrittura dei servizi, poiché non è necessario preoccuparsi della scalabilità.
- L'aggiunta di nuovi servizi non richiede modifiche ai vecchi, rendendo quindi più facile l'integrazione di nuovi servizi.
Come puoi vedere, è più di un semplice REST. Abbiamo ottenuto un insieme di strumenti che consente di lavorare con i dati comuni in modo decentralizzato.
Nell'articolo di oggi non sono stati trattati tutti gli aspetti. Dobbiamo ancora capire come bilanciare tra il paradigma 'richiesta-risposta' e il paradigma orientato agli eventi. Ma su questo ne parleremo la prossima volta. Ci sono argomenti da esplorare, come perché lo Stateful Stream Processing è così efficace. Di questo parleremo nel terzo articolo. Ci sono anche altre potenti strutture di cui possiamo avvalerci, come ad esempio, . Con il suo aiuto cambiano le regole del gioco per i sistemi di business distribuiti, poiché questa struttura garantisce transazioni per una forma scalabile. Questo sarà l'argomento del quarto articolo. E infine, dovremo esaminare i dettagli dell'implementazione di questi principi.

Ma per ora ricorda semplicemente questo: la dicotomia dei dati è la forza con cui ci confrontiamo nella creazione di servizi aziendali. E dobbiamo tenerne conto. Il focus è quello di ribaltare tutto e iniziare a considerare i dati comuni come oggetti di prima classe. Il Stateful Stream Processing offre un compromesso unico per questo. Evita i 'God Components' centralizzati che ostacolano il progresso. Inoltre, assicura l'agilità, la scalabilità e la resilienza delle pipeline di streaming dei dati e le integra in ogni servizio. Pertanto, possiamo concentrarci su un flusso comune di coscienza a cui può connettersi qualsiasi servizio e lavorare con i suoi dati. In questo modo, i servizi diventano più scalabili, intercambiabili e autonomi. Pertanto, non solo appariranno bene nelle bacheche e durante la verifica delle ipotesi, ma funzioneranno e cresceranno per decenni.
Fonte: habr.com
