Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Suggerimento
Questo contenuto è un estratto dell'eBook, Architettura di microservizi .NET per applicazioni .NET containerizzati, disponibile in documentazione .NET o come PDF scaricabile gratuitamente leggibile offline.
In un'applicazione monolitica in esecuzione in un singolo processo, i componenti richiamano l'uno l'altro usando il metodo a livello di linguaggio o le chiamate di funzione. Questi possono essere strettamente associati se si creano oggetti con codice (ad esempio, new ClassName()
) o possono essere richiamati in modo disaccoppiato se si usa l'iniezione delle dipendenze facendo riferimento alle astrazioni anziché a istanze di oggetti concreti. In entrambi i casi, gli oggetti vengono eseguiti all'interno dello stesso processo. La sfida principale quando si passa da un'applicazione monolitica a un'applicazione basata su microservizi consiste nel modificare il meccanismo di comunicazione. Una conversione diretta dalle chiamate ai metodi in-process alle chiamate RPC ai servizi causerà una comunicazione verbosa e inefficiente che non si comporterà bene negli ambienti distribuiti. Le sfide della progettazione corretta del sistema distribuito sono abbastanza note che esiste anche un canone noto come Fallacies of distributed computing che elenca i presupposti che gli sviluppatori spesso fanno quando si passa da monolitica a progettazioni distribuite.
Non c'è una soluzione, ma diverse. Una soluzione prevede l'isolamento dei microservizi aziendali il più possibile. Si usa quindi la comunicazione asincrona tra i microservizi interni e si sostituisce la comunicazione a grana fine, tipica nella comunicazione intra-processo tra oggetti, con una comunicazione a grana grossolana. È possibile eseguire questa operazione raggruppando le chiamate e restituendo dati che aggregano i risultati di più chiamate interne al client.
Un'applicazione basata su microservizi è un sistema distribuito in esecuzione su più processi o servizi, in genere anche in più server o host. Ogni istanza del servizio è in genere un processo. Pertanto, i servizi devono interagire usando un protocollo di comunicazione tra processi, ad esempio HTTP, AMQP o un protocollo binario come TCP, a seconda della natura di ogni servizio.
La community di microservizi promuove la filosofia di "endpoint intelligenti e pipe stupide". Questo slogan incoraggia un design che sia il più possibile disaccoppiato tra microservizi e il più coeso possibile all'interno di un singolo microservizio. Come spiegato in precedenza, ogni microservizio possiede i propri dati e la propria logica di dominio. Tuttavia, i microservizi che compongono un'applicazione end-to-end sono in genere semplicemente coreografati usando comunicazioni REST anziché protocolli complessi come WS-* e comunicazioni flessibili basate su eventi invece di orchestratori centralizzati di processi aziendali.
I due protocolli comunemente usati sono richiesta/risposta HTTP con API di risorse (quando si eseguono query per la maggior parte) e messaggistica asincrona leggera durante la comunicazione degli aggiornamenti tra più microservizi. Queste informazioni sono illustrate in modo più dettagliato nelle sezioni seguenti.
Tipi di comunicazione
I servizi e i client possono comunicare attraverso molti tipi diversi di comunicazione, ognuno destinato a uno scenario e a obiettivi diversi. Inizialmente, questi tipi di comunicazioni possono essere classificati in due assi.
Il primo asse definisce se il protocollo è sincrono o asincrono:
Protocollo sincrono. HTTP è un protocollo sincrono. Il client invia una richiesta e attende una risposta dal servizio. Indipendentemente dall'esecuzione del codice client che potrebbe essere sincrona (il thread è bloccato) o asincrono (il thread non è bloccato e la risposta raggiungerà infine un callback). Il punto importante è che il protocollo (HTTP/HTTPS) è sincrono e il codice client può continuare l'attività solo quando riceve la risposta del server HTTP.
Protocollo asincrono. Altri protocolli come AMQP (un protocollo supportato da molti sistemi operativi e ambienti cloud) usano messaggi asincroni. Il codice client o il mittente del messaggio in genere non attende una risposta. Invia semplicemente il messaggio come quando si invia un messaggio a una coda RabbitMQ o a qualsiasi altro broker di messaggi.
Il secondo asse definisce se la comunicazione ha un singolo ricevitore o più ricevitori:
Ricevitore singolo. Ogni richiesta deve essere elaborata esattamente da un ricevitore o da un servizio. Un esempio di questa comunicazione è il modello di comando.
Più ricevitori. Ogni richiesta può essere elaborata da zero a più ricevitori. Questo tipo di comunicazione deve essere asincrono. Un esempio è il meccanismo di pubblicazione/sottoscrizione usato nei modelli come l'architettura guidata dagli eventi. Si basa su un'interfaccia del bus di eventi o un broker di messaggi durante la propagazione degli aggiornamenti dei dati tra più microservizi tramite eventi; viene in genere implementato tramite un bus di servizio o un artefatto simile come il bus di servizio di Azure usando argomenti e sottoscrizioni.
Un'applicazione basata su microservizi userà spesso una combinazione di questi stili di comunicazione. Il tipo più comune è la comunicazione a ricevitore singolo con un protocollo sincrono come HTTP/HTTPS quando si richiama un normale servizio HTTP API Web. I microservizi usano in genere anche protocolli di messaggistica per la comunicazione asincrona tra microservizi.
Questi assi sono buoni da conoscere in modo da avere chiarezza sui possibili meccanismi di comunicazione, ma non sono i problemi importanti durante la creazione di microservizi. Né la natura asincrona dell'esecuzione del thread client né la natura asincrona del protocollo selezionato sono i punti importanti durante l'integrazione di microservizi. Ciò che è importante è poter integrare i microservizi in modo asincrono mantenendo l'indipendenza dei microservizi, come illustrato nella sezione seguente.
L'integrazione asincrona dei microservizi applica l'autonomia del microservizio
Come accennato, il punto importante quando si compila un'applicazione basata su microservizi è il modo in cui si integrano i microservizi. Idealmente, è consigliabile provare a ridurre al minimo la comunicazione tra i microservizi interni. Minore è il numero di comunicazioni tra microservizi, meglio è. In molti casi, tuttavia, sarà necessario integrare in qualche modo i microservizi. Quando è necessario eseguire questa operazione, la regola critica è che la comunicazione tra i microservizi deve essere asincrona. Ciò non significa che è necessario usare un protocollo specifico (ad esempio, la messaggistica asincrona rispetto a HTTP sincrono). Significa semplicemente che la comunicazione tra microservizi deve essere eseguita solo propagando i dati in modo asincrono, ma provare a non dipendere da altri microservizi interni come parte dell'operazione http di richiesta/risposta del servizio iniziale.
Se possibile, non dipende mai dalla comunicazione sincrona (richiesta/risposta) tra più microservizi, non anche per le query. L'obiettivo di ogni microservizio è essere autonomo e disponibile per il cliente consumatore, anche se gli altri servizi che fanno parte dell'applicazione end-to-end sono inattivi o malfunzionanti. Se si ritiene di dover effettuare una chiamata da un microservizio ad altri microservizi (ad esempio l'esecuzione di una richiesta HTTP per una query di dati) per poter fornire una risposta a un'applicazione client, si dispone di un'architettura che non sarà resiliente quando alcuni microservizi hanno esito negativo.
Inoltre, la presenza di dipendenze HTTP tra microservizi, ad esempio quando si creano cicli di richiesta/risposta lunghi con catene di richieste HTTP, come illustrato nella prima parte della figura 4-15, non solo rende i microservizi non autonomi, ma anche le prestazioni vengono influenzate non appena uno dei servizi in tale catena non ha prestazioni ottimali.
Più si aggiungono dipendenze sincrone tra microservizi, ad esempio le richieste di query, il peggio è il tempo di risposta complessivo per le app client.
Figura 4-15. Modelli e anti-pattern nella comunicazione tra microservizi
Come illustrato nel diagramma precedente, nella comunicazione sincrona viene creata una "catena" di richieste tra microservizi durante la gestione della richiesta client. Si tratta di un anti-modello. Nei microservizi di comunicazione asincrona usano messaggi asincroni o il polling HTTP per comunicare con altri microservizi, ma la richiesta client viene servita immediatamente.
Se il microservizio deve generare un'azione aggiuntiva in un altro microservizio, se possibile, non eseguire tale azione in modo sincrono e come parte dell'operazione di richiesta e risposta del microservizio originale. Eseguire invece questa operazione in modo asincrono (usando eventi di integrazione o messaggistica asincrona, code e così via). Tuttavia, per quanto possibile, non richiamare l'azione in modo sincrono nell'ambito dell'operazione di richiesta e risposta sincrona originale.
E infine (ed è qui che si verificano la maggior parte dei problemi durante la compilazione di microservizi), se il microservizio iniziale necessita di dati di proprietà di altri microservizi, non fare affidamento sull'esecuzione di richieste sincrone per tali dati. Replicare o propagare invece i dati (solo gli attributi necessari) nel database del servizio iniziale usando la coerenza finale (in genere usando eventi di integrazione, come illustrato nelle sezioni future).
Come indicato in precedenza nella sezione Identificazione dei limiti del modello di dominio per ogni microservizio , la duplicazione di alcuni dati in diversi microservizi non è una progettazione errata, al contrario, quando si possono convertire i dati in una lingua o termini specifici di tale dominio aggiuntivo o contesto delimitato. Ad esempio, nell'applicazione eShopOnContainers si dispone di un microservizio denominato identity-api
responsabile della maggior parte dei dati dell'utente con un'entità denominata User
. Tuttavia, quando è necessario archiviare i dati relativi all'utente all'interno del Ordering
microservizio, archiviarlo come entità diversa denominata Buyer
. L'entità Buyer
condivide la stessa identità con l'entità originale User
, ma potrebbe avere solo gli attributi necessari per il Ordering
dominio e non l'intero profilo utente.
È possibile usare qualsiasi protocollo per comunicare e propagare i dati in modo asincrono tra microservizi per garantire la coerenza finale. Come accennato, è possibile usare eventi di integrazione usando un bus di eventi o un broker di messaggi oppure è anche possibile usare HTTP eseguendo il polling degli altri servizi. Non importa. La regola importante consiste nel non creare dipendenze sincrone tra i microservizi.
Le sezioni seguenti illustrano i diversi stili di comunicazione che è possibile prendere in considerazione usando in un'applicazione basata su microservizi.
Stili di comunicazione
Esistono molti protocolli e scelte che è possibile usare per la comunicazione, a seconda del tipo di comunicazione che si vuole usare. Se si usa un meccanismo di comunicazione sincrono basato su richiesta/risposta, i protocolli come HTTP e REST sono i più comuni, soprattutto se si pubblicano i servizi all'esterno dell'host Docker o del cluster di microservizi. Se si comunica internamente tra servizi (all'interno dell'host Docker o del cluster di microservizi), è anche possibile usare meccanismi di comunicazione in formato binario, ad esempio WCF tramite TCP e formato binario. In alternativa, è possibile usare meccanismi di comunicazione asincroni basati su messaggi, ad esempio AMQP.
Esistono anche più formati di messaggio, ad esempio JSON o XML, o anche formati binari, che possono essere più efficienti. Se il formato binario scelto non è uno standard, probabilmente non è consigliabile pubblicare pubblicamente i servizi usando tale formato. È possibile usare un formato non standard per la comunicazione interna tra i microservizi. Questa operazione può essere eseguita quando si comunica tra microservizi all'interno dell'host Docker o del cluster di microservizi (ad esempio, agenti di orchestrazione Docker) o per le applicazioni client proprietarie che comunicano con i microservizi.
Comunicazione di richiesta/risposta con HTTP e REST
Quando un client usa la comunicazione di richiesta/risposta, invia una richiesta a un servizio, quindi il servizio elabora la richiesta e invia una risposta. La comunicazione di richiesta/risposta è particolarmente adatta per l'interrogazione dei dati per un'interfaccia utente in tempo reale (un'interfaccia utente attiva) dalle app client. Pertanto, in un'architettura di microservizi si userà probabilmente questo meccanismo di comunicazione per la maggior parte delle query, come illustrato nella figura 4-16.
Figura 4-16. Uso della comunicazione di richiesta/risposta HTTP (sincrona o asincrona)
Quando un client usa la comunicazione di richiesta/risposta, presuppone che la risposta arrivi in un breve periodo di tempo, in genere inferiore a un secondo o di alcuni secondi al massimo. Per le risposte ritardate, è necessario implementare la comunicazione asincrona in base ai modelli di messaggistica e alle tecnologie di messaggistica, un approccio diverso illustrato nella sezione successiva.
Uno stile architettonico comune per la comunicazione di richiesta/risposta è REST. Questo approccio si basa su e strettamente accoppiato a, il protocollo HTTP , adottando verbi HTTP come GET, POST e PUT. REST è l'approccio di comunicazione architetturale più comunemente usato durante la creazione di servizi. È possibile implementare i servizi REST quando si sviluppano ASP.NET servizi API Web core.
Quando si usano servizi REST HTTP come linguaggio di definizione dell'interfaccia, è disponibile un valore aggiuntivo. Ad esempio, se si usano metadati Swagger per descrivere l'API del servizio, è possibile usare strumenti che generano stub client che possono individuare e utilizzare direttamente i servizi.
Risorse aggiuntive
Martin Fowler. Modello di maturità richardson Descrizione del modello REST.
https://martinfowler.com/articles/richardsonMaturityModel.htmlSwagger Il sito ufficiale.
https://swagger.io/
Comunicazione push e in tempo reale basata su HTTP
Un'altra possibilità (in genere per scopi diversi rispetto a REST) è una comunicazione in tempo reale e uno-a-molti con framework di livello superiore, ad esempio ASP.NET SignalR e protocolli come WebSocket.
Come illustrato nella figura 4-17, la comunicazione HTTP in tempo reale significa che è possibile disporre del codice del server che esegue il push del contenuto ai client connessi man mano che i dati diventano disponibili, invece di fare in modo che il server attenda che un client richieda nuovi dati.
Figura 4-17. Comunicazione asincrona di messaggi uno-a-molti in tempo reale
SignalR è un buon modo per ottenere comunicazioni in tempo reale per il push del contenuto ai client da un server back-end. Poiché la comunicazione è in tempo reale, le app client mostrano le modifiche quasi immediatamente. Questo viene in genere gestito da un protocollo, ad esempio WebSocket, che usa molte connessioni WebSocket (una per client). Un esempio tipico è quando un servizio comunica contemporaneamente una modifica del punteggio di un gioco sportivo a molte app Web client.