Messaging in a Service-Oriented Architecture

Pubblicato il

(Joan Zapata) (8 dicembre 2020)

Service-Oriented Architecture (SOA) ha dimostrato i suoi punti di forza in quasi tutti i settori per produrre sistemi scalabili ed evolutivi. SOA ha molti significati, ma la sua idea fondamentale di suddividere unapplicazione complessa in servizi più piccoli, riutilizzabili e liberamente accoppiati ha fatto molta strada dalla fine degli anni 90. Abbiamo abbracciato il concetto dal primo giorno e dopo tre anni, ora abbiamo una ventina di servizi disaccoppiati gestiti dal dominio che strutturano le nostre applicazioni di backend. Ci ha permesso di utilizzare le lingue che ci piacevano, come Kotlin , Elixir – e gli strumenti volevamo – come Event Sourcing – per lattività da svolgere.

Ma avere più servizi comporta le sue sfide, la prima è come fare in modo che i servizi comunichino tra loro ? Ci sono molte strategie là fuori e questo articolo è un ritorno sullesperienza di quelle che usiamo in Memo Bank.

Cominciamo con due servizi e questo semplice caso duso come esempio:

REST su HTTPS

Come molte applicazioni di backend, abbiamo iniziato con REST chiamate su HTTPS.

Dipendenze cicliche tra i servizi devono essere evitate a tutti i costi, quindi contratti non può dire direttamente ai clienti di attivare il nuovo cliente non appena viene firmato il contratto. Invece, i clienti devono inviare regolarmente richieste ai contratti per sapere quando lattività è terminata.

HTTPS è un ottimo inizio e sicuramente qualcosa da tenere nella cassetta degli attrezzi: è semplice da configurare, ampiamente supportato e utilizzato anche per esporre le API alle applicazioni frontend. Ma cè un grosso problema con questo schema, è sincrono (anche se non necessariamente bloccante). Significa che mentre i contratti funzionano, i clienti sono in attesa con una connessione attiva. Se utilizzato su larga scala, significa che il servizio più lento può rallentare lintero sistema. Significa anche che quando il servizio contratti non è disponibile, non lo è neanche il servizio clienti , che si chiama fallimento a cascata.

Come abbiamo affrontato questa edizione? Abbiamo utilizzato due modelli diversi: messaggistica discreta e continua.

Messaggistica discreta

Prendiamo il primo caso duso: i clienti vogliono contratti per inviare un contratto.

Per questo, possiamo risolvere il problema utilizzando un meccanismo di messaggistica discreto: queues . È possibile visualizzare una coda come una cassetta postale di terze parti ad alta disponibilità. Anche quando il servizio principale non è disponibile, la casella di posta accetta i messaggi. Più istanze dello stesso servizio possono utilizzare la stessa coda, nel qual caso la coda funge da bilanciatore del carico e garantisce che ogni messaggio venga gestito almeno una volta. Una volta che un messaggio è stato gestito, la casella di posta può dimenticarsene.

Usiamo code per inviare comandi (ad es. “SendContract”) da un servizio allaltro. Presenta diversi vantaggi:

  • Il servizio chiamante non si basa sulla disponibilità di il servizio ricevente e può riprendere a funzionare non appena il comando è in coda;
  • Il destinatario può gestire i comandi al proprio ritmo e possiamo facilmente aumentare o diminuire il servizio di ricezione a seconda del carico delle sue code;
  • Come bonus, failure può essere facilmente isolato e gestito manualmente (speriamo di trattare largomento dead letter queues in un altro articolo).

Messaggistica continua

Ora vediamo il secondo caso duso, quando il contratto viene firmato, i contratti devono farlo sapere ai clienti è successo in modo che possa attivare il cliente.

Si è tentati di utilizzare unaltra coda qui, ma come abbiamo detto prima non vogliamo una dipendenza ciclica, quindi contratti non lo sa nulla sui clienti . Pertanto, non può inviare un comando “ActivateCustomer” e non può sapere quando o dove inviarlo.

Abbiamo risolto questo problema utilizzando un meccanismo di messaggistica continua: stream .Puoi vedere uno stream come una sequenza ordinata di eventi che possono essere consumati in tempo reale ma sono anche resi disponibili nel tempo. Contrariamente alle code che sono comandi temporanei non correlati, gli stream raccontano una storia persistente .

Ogni servizio di Memo Bank trasmette un flusso di eventi che descrivono il ciclo di vita delle sue risorse. La creazione e la gestione di questi eventi è parte integrante di qualsiasi sviluppo , indipendentemente dal fatto che questo evento sia immediatamente necessario o meno. Fa parte della routine, proprio come i test e le metriche automatizzati.

Questi eventi diventano così un log con timestamp affidabile di immutabile fatti . E poiché fanno parte dellAPI del servizio emittente, possono essere consumati da qualsiasi altro servizio, sia in tempo reale (uno per uno) che in futuro. Questi eventi hanno un valore immenso per la tracciabilità e analisi dei dati .

Riassumendo:

  • Le code vengono utilizzate per inviare comandi a un servizio specifico;
  • Gli stream vengono utilizzati per esporre fatti da un servizio specifico.

Tutto sulle dipendenze

Il diagramma sopra potrebbe farti pensare, anche se guardando le frecce, non introduce un dipendenza ciclica tra clienti e contratti?

No non lo fa. Una dipendenza non è definita dalla direzione dei dati, ma dai servizi di conoscenza luno dellaltro. Qui, i clienti conoscono i contratti , gli dice cosa fare e ne ascolta la storia. Ma contratti non sa nulla di clienti , non ha bisogno di sapere chi invia i comandi, né chi sta ascoltando la sua storia.

Entrambi la coda e lo stream fanno parte dei contratti API e i clienti dipendono da questa API.

Avere un denominazione convenzione per comandi e fatti è molto importante per trasmettere questa idea. Usiamo sempre un modulo di base per i comandi, come “SendContract”, e un modulo participio passato per i fatti, come “ContractSent”.

Tieni presente che corrisponde convenientemente allarchitettura del nostro sistema bancario principale basato su CQRS / ES . In questa terminologia, i comandi sono gli stessi e gli eventi sono fatti.

Come scegliere la direzione della dipendenza

Dati i principi spiegati prima, questa soluzione sarebbe altrettanto valida:

Ma se entrambe le soluzioni sono valide, come sceglierne una rispetto allaltra? Bene, dipende da te.

Tutto si riduce a la direzione della dipendenza che desideri da impostare.

A: clienti dipende dai contratti . B: contratti dipende dai clienti .

Ecco alcune domande che di solito ci poniamo:

  • Un servizio può essere facilmente indipendente dagli altri?
    Qui, ad esempio, purché diamo il contenuto a contratti , I contratti possono essere totalmente indipendenti dal servizio che li utilizza. È più difficile immaginare che i clienti siano agnostici sul fatto che richiede un contratto. È a favore di A.
  • E se un servizio fosse di terze parti?
    Per Ad esempio, non avrebbe senso che Memo Bank esternalizzasse clienti ma potrebbe farlo per contratti . Pertanto, anche questo è a favore di A.
  • Un servizio sta orchestrando altri servizi?
    Lorchestrazione implicita è pessima, puoi trovare ulteriori informazioni a riguardo in questo discorso di Bernd Ruecker. La creazione di un cliente è un flusso di lavoro complesso che coinvolge molti servizi (invio di e-mail, notifiche, creazione di un conto bancario, ecc.), Quindi clienti è probabilmente un orchestratore qui.Fare in modo che lorchestrator dipenda da altri servizi, e non viceversa, rende il codice molto più facile da capire, perché lintero flusso di lavoro può essere trovato in un unico posto. Questo è anche a favore di A.
  • Crea un ciclo nellarchitettura complessiva?
    Anche se non esiste alcun collegamento tra i due servizi, entrambi dipendono da altri servizi. Supponiamo che clienti dipenda da utenti e che utenti dipenda già da contratti . Se scegliessimo la soluzione B, creerebbe un ciclo con i tre servizi. Questo è anche a favore di A.

Conclusione

La messaggistica è una delle prime domande a cui dobbiamo rispondere quando creiamo unarchitettura orientata ai servizi. Lutilizzo di HTTPS e REST allinizio sembra la soluzione più semplice, ma ha i suoi limiti. Abbiamo completato il nostro arsenale con code e stream e stabiliamo principalmente due linee guida.

Innanzitutto, ogni servizio deve trasmettere in streaming eventi quando i fatti si verificano allinterno di questo servizio, anche se non abbiamo ancora bisogno di questi eventi. Questi eventi dovrebbero raccontare una storia di ciò che accade nel servizio, ad esempio “ContractSent”, “ContractSigned”. Questo è ottimo per la tracciabilità, richiesta come banca, ma anche per consolidare lAPI di ogni servizio e per rendere il sistema più facile da utilizzare per tutti i team.

In secondo luogo, si tratta di dipendenze . Le dipendenze modellano il sistema e le dipendenze cicliche sono il nemico numero uno. Una volta impostate correttamente le dipendenze, gli strumenti di messaggistica sono qui solo per consentire ai dati di fluire in qualsiasi direzione.

Originariamente pubblicato su https://memo.bank/en/magazine .

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *