Condivisione delle informazioni nei sistemi basati su eventi

6

Sto sviluppando un sistema software, che fornisce API REST HTTP, e voglio ottenere un design molto modulare e flessibile. Quindi, c'è una funzionalità di base e moduli di funzionalità, che gestiscono solo la logica relativa a ciascuna caratteristica specifica. L'obiettivo è essere in grado di aggiungere / rimuovere moduli in modo flessibile senza la necessità di connettere manualmente ciascun modulo al core e ad altri moduli. Pertanto i moduli non dovrebbero avere dipendenze strette tra loro e il core dovrebbe essere molto piccolo e non dovrebbe dipendere da nessuna funzione.

Ho scoperto che l'architettura basata su eventi è un buon candidato per i miei obiettivi.

Tuttavia, ho alcune difficoltà a implementarlo in maniera robusta.

1. Come avvicinarsi alla condivisione delle informazioni?

Inizialmente, quando viene eseguita una logica all'interno dell'applicazione (ad esempio durante l'elaborazione della richiesta utente), esiste un contesto attorno ad essa. Ad esempio, alcune entità sono state caricate dal database (sto usando la libreria ORM per questo). E poi voglio lanciare un evento.

Quali dati dovrei passare con l'evento? Posso vedere due approcci:

  • Passa alle entità correlate con l'evento
  • Passa solo gli ID entità (cioè la chiave primaria, che potrebbe essere utilizzata per recuperare l'entità dal database)

Con il primo approccio, i dati potrebbero essere utilizzati subito dall'evento, questo è conveniente. Tuttavia, cosa succede se qualche evento precedente ha apportato alcune modifiche ai dati? Non possiamo saperlo con certezza, perché i gestori degli eventi sono disaccoppiati. Quindi i dati passati potrebbero essere già superati.

Con il secondo approccio stiamo forzando il caricamento di nuovi dati dal database, poiché inviamo solo ID e non dati reali. Quindi ogni gestore di eventi dovrà recuperare manualmente le entità. Questo risolve il problema della freschezza dei dati, ma porta ad un altro problema in termini di prestazioni. Potrebbero esserci dozzine di gestori di eventi che reagiscono a una singola richiesta dell'utente e ognuno invierà richieste al database. Questo sembra un approccio molto inefficace, che metterà l'accento sul database.

2. Come gestire la concorrenza (isolamento della transazione)?

Un altro problema è che dobbiamo assicurarci che il nostro codice sia al sicuro dal punto di vista della concorrenza. Non vogliamo che richieste parallele o anche parti diverse dello stesso processo modifichino gli stessi dati. Per fare ciò, dobbiamo utilizzare le transazioni del database e varie funzionalità di blocco.

E ancora, potremmo avviare una transazione e passarla con l'evento, così il gestore di eventi sarà in grado di utilizzare lo stesso livello di isolamento. O ogni gestore di eventi avrebbe la propria transazione. Ma questo porta di nuovo alla questione delle prestazioni del database.

Stavo pensando di creare una nuova transazione per ogni richiesta utente e di passarla a ciascun metodo, che richiede l'accesso al database (direttamente o con gli eventi). Pertanto ogni richiesta con tutti i possibili gestori di eventi verrà eseguita in un'unica transazione. È un buon approccio?

In generale, questa architettura solleva molte domande, sarei molto grato per qualsiasi buon libro o articolo su questo argomento. O forse c'è un approccio migliore per raggiungere i miei obiettivi in termini di flessibilità e modularità?

    
posta Slava Fomin II 16.12.2017 - 19:39
fonte

2 risposte

2

How to handle concurrency (transaction isolation)?

Se vuoi avere un sistema modulare disaccoppiato, il modo migliore per farlo è dividere la funzionalità in moduli / servizi che non richiedono la comunicazione sincrona per adempiere alla loro funzione.

Sì, questo squalifica tutti i microservizi basati su CRUD, che sono fondamentalmente solo tabelle con un'API HTTP.

Ciò significa che tutti gli eventi dovrebbero essere tipo fire-and-forget di eventi. Pertanto, i limiti delle transazioni non dovrebbero mai superare i limiti del servizio, quindi in genere non è necessaria una transazione distribuita.

How to approach the information sharing?

Non passare le chiavi primarie nei messaggi se stai facendo HTTP RESTful. Passa un URI in cui la risorsa è disponibile.

Ma ancora una volta, non dovresti passare nulla che richiederebbe al peer ricevente di restituire una query o qualcos'altro. Dividi la funzionalità in modo che ogni messaggio possa essere gestito interamente dal destinatario.

Ecco qualche altra descrizione di questo stile: Sistemi autonomi .

    
risposta data 16.12.2017 - 21:13
fonte
1

What data should I pass with the event?

Sicuramente solo id. L'evento è una notifica che qualcosa è successo, non è destinato a essere un contenitore di dati. Questo approccio si traduce in un accoppiamento lento, quindi quando si scopre che il database non gestisce il carico, non impiegherà troppo sforzo per spostare tale funzionalità in una macchina separata con un proprio database.

Ma trovo il tuo commento sul riutilizzo dei dati un po 'sospetto. La maggior parte delle volte, un evento separa attività piuttosto diverse. Indica che qualcosa è accaduto dal punto di vista del business, il che implica che qualche coerente (sub-) feature o processo di business ha funzionato, e ne segue un altro passo. Più in generale, questo rappresenta un concetto di saga . Ciò implica che ogni fase viene eseguita indipendentemente da qualsiasi altro, probabilmente ognuno dei quali risiede sulla propria macchina con un proprio database.

Un altro caso valido per un evento è un concetto ddd di evento di dominio. Di solito implica che un caso d'uso viene diviso in alcuni sottomessaggi e ogni sottofase è implementata nel proprio servizio di applicazione, sebbene ogni passaggio sia una parte di una transazione di database. Ecco un esempio di quella tecnica.

  1. How to handle concurrency (transaction isolation)?

Suppongo che ci siano due punti importanti da menzionare. Il primo è il concetto di consistenza finale . Fondamentalmente si tratta di ciò che ho già trattato: suddividere il caso d'uso in passaggi secondari e trattare ogni ACID-ly. In modo da non avere transazioni enormi, che è un modo per aggancio stretto e deadlock.

Il secondo riguarda l'identificazione dei confini del servizio. Quello che hai chiamato moduli è un (micro) servizio. Quindi il tuo obiettivo principale è identificare i confini del tuo servizio in modo che siano autonomi.

La serie di post che trattano questi argomenti potrebbe essere di un certo interesse per te .

    
risposta data 16.12.2017 - 20:16
fonte