Database di microservizi di seeding


10

Dato il servizio A (CMS) che controlla un modello (Prodotto, supponiamo che gli unici campi che ha sono ID, titolo, prezzo) e i servizi B (Spedizione) e C (Email) che devono visualizzare un determinato modello quale dovrebbe essere l'approccio sincronizzare le informazioni del modello dato attraverso quei servizi nell'approccio di approvvigionamento di eventi? Supponiamo che il catalogo dei prodotti cambi raramente (ma cambi) e che ci siano amministratori che possono accedere ai dati di spedizioni ed e-mail molto spesso (le funzionalità di esempio sono: B: display titles of products the order containede C:) display content of email about shipping that is going to be sent. Ognuno dei servizi ha il proprio DB.

Soluzione 1

Invia tutte le informazioni richieste sul Prodotto all'interno dell'evento - ciò significa la seguente struttura per order_placed:

{
    order_id: [guid],
    product: {
        id: [guid],
        title: 'Foo',
        price: 1000
    }
}

Le informazioni sul prodotto B e C del servizio sono archiviate nell'attributo productJSON nella orderstabella

Pertanto, per visualizzare le informazioni necessarie vengono utilizzati solo i dati recuperati dall'evento

Problemi : a seconda di quali altre informazioni devono essere presentate in B e C, la quantità di dati nell'evento può aumentare. B e C potrebbero non richiedere le stesse informazioni sul Prodotto, ma l'evento dovrà contenere entrambi (a meno che non separiamo gli eventi in due). Se determinati dati non sono presenti all'interno di un determinato evento, il codice non può utilizzarli - se aggiungeremo un'opzione di colore a un determinato Prodotto, per gli ordini esistenti in B e C, un determinato prodotto sarà incolore a meno che non aggiorniamo gli eventi e quindi li eseguiamo nuovamente .

Soluzione 2

Invia solo guida del prodotto all'interno dell'evento - ciò significa la seguente struttura per order_placed:

{
    order_id: [guid],
    product_id: [guid]
}

Sui servizi le informazioni sui prodotti B e C sono memorizzate product_idnell'attributo nella orderstabella

Le informazioni sul prodotto vengono recuperate dai servizi B e C quando richiesto eseguendo una chiamata API A/product/[guid]all'endpoint

Problemi : questo rende B e C dipendenti da A (in ogni momento). Se lo schema del Prodotto cambia su A, le modifiche devono essere fatte su tutti i servizi che dipendono da loro (improvvisamente)

Soluzione 3

Invia solo una guida al prodotto all'interno dell'evento - questo significa la seguente struttura per order_placed:

{
    order_id: [guid],
    product_id: [guid]
}

Sui servizi B e C le informazioni sul prodotto sono memorizzate nella productstabella; c'è ancora product_idsul orderstavolo, ma c'è la replica dei productsdati tra A, B e C; B e C potrebbero contenere informazioni diverse sul Prodotto rispetto ad A

Le informazioni sul prodotto vengono trasmesse quando vengono creati i servizi B e C e vengono aggiornati ogni volta che le informazioni sui prodotti cambiano effettuando una chiamata A/productall'endpoint (che visualizza le informazioni richieste su tutti i prodotti) o eseguendo un accesso diretto al database A e copiando le informazioni necessarie sul prodotto richieste servizio.

Problemi : questo rende B e C dipendenti da A (durante la semina). Se lo schema del Prodotto cambia su A, le modifiche devono essere fatte su tutti i servizi che dipendono da loro (durante il seeding)


Secondo la mia comprensione, l'approccio corretto sarebbe quello di andare con la soluzione 1 e aggiornare la cronologia degli eventi secondo una certa logica (se il catalogo dei prodotti non è cambiato e vogliamo aggiungere il colore da visualizzare, possiamo aggiornare la cronologia in modo sicuro per ottenere lo stato corrente dei prodotti e riempire i dati mancanti all'interno degli eventi) o provvedere alla non esistenza di dati dati (se il catalogo dei prodotti è cambiato e vogliamo aggiungere il colore da visualizzare, non possiamo essere sicuri se a quel punto nel passato in quel dato prodotto aveva un colore o no - possiamo presumere che tutti i prodotti nel catalogo precedente fossero neri e approvati aggiornando eventi o codice)


Per quanto riguarda updating event history- In caso di approvvigionamento, la cronologia degli eventi è la tua fonte di verità e non dovrebbe mai essere modificata, ma solo andare avanti. Se gli eventi cambiano, è possibile utilizzare il versioning degli eventi o soluzioni simili, ma quando si riproducono gli eventi fino a un determinato momento, lo stato dei dati dovrebbe essere come era a quel punto.
No, il

Per quanto riguarda l'archiviazione dei dati (schemi, ecc.) Per l'interrogazione e l'aggiunta / rimozione di campi ecc. Ci siamo utilizzati cosmosDB per archiviare i dati in JSON così com'è in quel momento. L'unica cosa che necessita quindi del controllo delle versioni è eventi e / o comandi. È inoltre necessario aggiornare i contratti endpoint e gli oggetti valore contenenti i dati che rispondono alle query di un client (web, mobile, ecc ...). I dati più vecchi che non hanno un campo avranno un valore predefinito o vuoto, che si adatta sempre al business ma la cronologia degli eventi rimane intatta e va avanti.
No, il

@Nope, updating event historyintendo: esaminare tutti gli eventi, copiandoli da uno stream (v1) a un altro stream (v2) per mantenere uno schema di eventi coerente.
Eithed

A parte questo, nel regno del commercio / e-commerce, potresti voler catturare il prezzo come indicato dato che i prezzi cambiano frequentemente. Un prezzo visualizzato per l'utente potrebbe essere diverso nel momento in cui viene acquisito l'effettivo ordine. Esistono diversi modi per risolvere il problema, ma è uno che dovrebbe essere considerato.
CPerson

@CPerson yup - il prezzo potrebbe essere uno degli attributi passati all'interno dell'evento stesso. D'altra parte, l'URL per l'immagine può esistere all'interno dell'evento (che rappresenta l'intento di display image at the point when purchase was made) o no (che rappresenta l'intento di display current image as it within catalog)
eithed

Risposte:


3

La soluzione n. 3 è davvero vicina all'idea giusta.

Un modo di pensare a questo: B e C stanno memorizzando nella cache copie "locali" dei dati di cui hanno bisogno. I messaggi elaborati in B (e similmente in C) utilizzano le informazioni memorizzate nella cache locale. Allo stesso modo, i report vengono prodotti utilizzando le informazioni memorizzate nella cache locale.

I dati vengono replicati dall'origine alle cache tramite un'API stabile. B e C non hanno nemmeno bisogno di usare la stessa API: usano qualunque protocollo di recupero sia appropriato per le loro esigenze. In effetti, definiamo un contratto - protocollo e schema dei messaggi - che vincola il fornitore e il consumatore. Quindi qualsiasi consumatore per quel contratto può essere collegato a qualsiasi fornitore. Le modifiche incompatibili all'indietro richiedono un nuovo contratto.

I servizi scelgono la strategia di invalidazione della cache appropriata per le loro esigenze. Ciò potrebbe significare estrarre le modifiche dall'origine su una pianificazione regolare, o in risposta a una notifica che le cose potrebbero essere cambiate, o addirittura "su richiesta" - agire come una cache di lettura, tornare alla copia memorizzata dei dati quando la fonte non è disponibile.

Questo ti dà "autonomia", nel senso che B e C possono continuare a fornire valore commerciale quando A è temporaneamente non disponibile.

Letture consigliate: dati all'esterno, dati all'interno , Pat Helland 2005.


Sì, sono completamente d'accordo con quello che hai scritto qui e la soluzione 3 è la soluzione goto che ho applicato, tuttavia, non è l'approccio di approvvigionamento di eventi, poiché, se riproduciamo gli eventi, non vogliamo necessariamente utilizzare lo stato corrente del prodotto; vogliamo usare lo stato com'era al punto dell'evento. Naturalmente, questo potrebbe andare bene (a seconda delle esigenze aziendali). Se, tuttavia, vogliamo tenere traccia delle modifiche al catalogo, ciò richiede anche l'approvvigionamento di eventi e dipende dalla quantità di dati disponibili, è meglio tornare alla soluzione 1.
eithed

1
Penso che tu l'abbia ottenuto con la soluzione n. 3. Se è necessario riprodurre la coerenza con il catalogo, anche l'origine dell'evento. Devi ripetere la riproduzione solo quando esegui di nuovo il seeding, che è probabilmente all'avvio: una volta che sei attivo devi solo guardare nuovi eventi, quindi la quantità di dati probabilmente non è un vero problema. Tuttavia, anche allora hai la possibilità (se necessario) di usare i checkpoint, cioè "qui è lo stato dell'evento 1.000", quindi prendi quello e ora devi solo riprodurre l'evento 1.001-attuale invece dell'intera cronologia .
Mike B.

2

Ci sono due cose difficili in Informatica, e una di queste è l'invalidazione della cache.

La soluzione 2 è assolutamente la mia posizione predefinita e in genere dovresti considerare l'implementazione della cache solo se ti imbatti in uno dei seguenti scenari:

  1. La chiamata API al servizio A sta causando problemi di prestazioni.
  2. Il costo del servizio A essere basso e non essere in grado di recuperare i dati è significativo per l'azienda.

I problemi di prestazioni sono davvero il driver principale. Esistono molti modi per risolvere il problema n. 2 che non implicano la memorizzazione nella cache, come garantire che il servizio A sia altamente disponibile.

La memorizzazione nella cache aggiunge una complessità significativa a un sistema e può creare casi limite di cui è difficile ragionare e bug che sono molto difficili da replicare. È inoltre necessario mitigare il rischio di fornire dati non aggiornati in presenza di dati più recenti, che possono essere molto peggiori dal punto di vista aziendale rispetto alla (ad esempio) visualizzazione di un messaggio che indica che il servizio A è inattivo: riprovare più tardi.

Da questo eccellente articolo di Udi Dahan:

Queste dipendenze si insinuano lentamente in te, legando insieme i lacci delle scarpe, rallentando gradualmente il ritmo di sviluppo, minando la stabilità della base di codice in cui le modifiche a una parte del sistema interrompono altre parti. È una morte lenta di mille tagli e, di conseguenza, nessuno è esattamente sicuro di quale grande decisione abbiamo preso che ha causato tutto così male.

Inoltre, se è necessaria una query temporizzata dei dati di prodotto, questo deve essere gestito nel modo in cui i dati sono memorizzati nel database del prodotto (ad es. Date di inizio / fine), deve essere chiaramente esposto nell'API (la data di validità deve essere un input per la chiamata API per interrogare i dati).


1
@SavvasKleanthous "La rete è affidabile" è uno degli errori del calcolo distribuito. Ma la risposta a questo errore non dovrebbe essere "cache ogni bit di dati da ogni servizio in ogni altro servizio" (mi rendo conto che è un po 'iperbolico). Aspettati che un servizio non sia disponibile e gestiscilo come una condizione di errore. Se si verifica una situazione rara in cui il servizio A in calo ha un impatto importante sul business, quindi (attentamente!) Prendere in considerazione altre opzioni.
Phil Sandler,

1
@SavvasKleanthous considera anche (come ho già detto nella mia risposta) che in molti casi la restituzione di dati non aggiornati può essere molto peggio che generare un errore.
Phil Sandler,

1
@eithed Mi riferivo a questo commento: "Se, tuttavia, vogliamo tenere traccia delle modifiche al catalogo, ciò richiede anche l'approvvigionamento di eventi". In ogni caso, hai la giusta idea: il servizio Prodotto dovrebbe essere responsabile del monitoraggio delle modifiche nel tempo, non dei servizi a valle.
Phil Sandler,

1
Inoltre, la memorizzazione dei dati osservati, pur presentando alcune somiglianze con la memorizzazione nella cache, non presenta gli stessi problemi. Più specificamente, non è necessaria l'invalidazione; ottieni la nuova versione dei dati quando succede. Ciò che si verifica è la coerenza ritardata. Tuttavia, anche usando la richiesta web c'è una finestra di incoerenza (anche se piccola).
Savvas Kleanthous

1
@SavvasKleanthous In ogni caso, il mio punto principale non è cercare di risolvere problemi che non esistono ancora, specialmente con soluzioni che portano i loro problemi e rischi. L'opzione 2 è la soluzione più semplice e dovrebbe essere la scelta predefinita fino a quando non soddisfa i requisiti aziendali . Se pensi che scegliere la soluzione più semplice che possa funzionare sia (come dici tu) "davvero pessima", allora penso che non siamo d'accordo.
Phil Sandler,

2

È molto difficile dire semplicemente che una soluzione è migliore dell'altra. Sceglierne una tra la soluzione 2 e 3 dipende da altri fattori (durata della cache, tolleranza di coerenza, ...)

I miei 2 centesimi:

L'invalidazione della cache potrebbe essere difficile ma l'istruzione del problema menziona che il catalogo dei prodotti cambia raramente. Questo fatto rende i dati di prodotto un buon candidato per la memorizzazione nella cache

Soluzione n. 1 (NOK)

  • I dati vengono duplicati su più sistemi

Soluzione n. 2 (OK)

  • Offre una forte coerenza
  • Funziona solo quando il servizio prodotti è altamente disponibile e offre buone prestazioni
  • Se il servizio di posta elettronica prepara un riepilogo (con molti prodotti), il tempo di risposta complessivo potrebbe essere più lungo

Soluzione n. 3 (complessa ma preferita)

  • Preferisci l'approccio API anziché l'accesso diretto al DB per recuperare informazioni sul prodotto
  • I servizi che richiedono resilienza non sono influenzati quando il servizio del prodotto è inattivo
  • Le applicazioni di consumo (servizi di spedizione ed e-mail) recuperano i dettagli del prodotto immediatamente dopo la pubblicazione di un evento. La possibilità che il servizio di assistenza diminuisca in pochi millisecondi è molto remota.

1

In generale, consiglio vivamente contro l'opzione 2 a causa dell'accoppiamento temporale tra questi due servizi (a meno che la comunicazione tra questi servizi sia super stabile e non molto frequente). L'accoppiamento temporale è ciò che descrivi come this makes B and C dependant upon A (at all times), e significa che se A è inattivo o non raggiungibile da B o C, B e C non possono svolgere la loro funzione.

Personalmente credo che entrambe le opzioni 1 e 3 abbiano situazioni in cui sono valide.

Se la comunicazione tra A e B e C è così elevata, o la quantità di dati necessari per partecipare all'evento è abbastanza grande da farne una preoccupazione, allora l'opzione 3 è l'opzione migliore, perché l'onere sulla rete è molto più basso e la latenza delle operazioni diminuirà al diminuire della dimensione del messaggio. Altre preoccupazioni da considerare qui sono:

  1. Stabilità del contratto: se il contratto di abbandono del messaggio A cambia spesso, l'inserimento di molte proprietà nel messaggio comporterebbe numerosi cambiamenti nei consumatori. Tuttavia, in questo caso credo che questo non sia un grosso problema perché:
    1. Hai detto che il sistema A è un CMS. Ciò significa che stai lavorando su un dominio stabile e come tale non credo che vedrai cambiamenti frequenti
    2. Poiché i B e C sono di spedizione ed e-mail e si stanno ricevendo dati da A, credo che si verifichino modifiche aggiuntive anziché interruzioni, che è possibile aggiungere in modo sicuro ogni volta che le si scopre senza rilavorazioni.
  2. Accoppiamento: qui c'è poco o nessun accoppiamento. Innanzitutto poiché la comunicazione avviene tramite messaggi, non esiste alcun accoppiamento tra i servizi oltre a un breve intervallo temporale durante il seeding dei dati e il contratto di tale operazione (che non è un accoppiamento che è possibile o che si dovrebbe cercare di evitare)

L'opzione 1 non è qualcosa che rifiuterei però. C'è la stessa quantità di accoppiamento, ma per quanto riguarda lo sviluppo dovrebbe essere facile da fare (non sono necessarie azioni speciali) e la stabilità del dominio dovrebbe significare che questi non cambieranno spesso (come ho già detto).

Un'altra opzione che suggerirei è una leggera variazione a 3, che non consiste nell'eseguire il processo durante l'avvio, ma osserva invece un evento "ProductAdded e" ProductDetailsChanged "su B e C, quando si verifica una modifica nel catalogo prodotti in A. Questo renderebbe le tue implementazioni più veloci (e così più facile da risolvere un problema / bug se ne trovi).


Modifica 2020-03-03

Ho un ordine specifico di priorità nel determinare l'approccio all'integrazione:

  1. Qual è il costo della coerenza? Possiamo accettare alcuni millisecondi di incoerenza tra le cose cambiate in A e quelle che si riflettono in B & C?
  2. Hai bisogno di query point-in-time (chiamate anche query temporali)?
  3. Esiste una fonte di verità per i dati? Un servizio che li possiede ed è considerato a monte?
  4. Se esiste un proprietario / unica fonte di verità è stabile? O ci aspettiamo di vedere frequenti cambiamenti di rottura?

Se il costo dell'incoerenza è elevato (sostanzialmente i dati del prodotto in A devono essere coerenti il ​​più presto possibile con il prodotto memorizzato nella cache in B e C), allora non puoi evitare di accettare la necessità e fare una richiesta sincrona (come un web / richiesta di riposo) da B&C ad A per recuperare i dati. Sii consapevole! Ciò non significa ancora una transazione coerente, ma riduce al minimo le finestre per incoerenza. Se devi assolutamente essere assolutamente coerente, devi ripetere i confini del tuo servizio. Tuttavia, sono molto fermamente questo non dovrebbe essere un problema. Per esperienza, in realtà è estremamente raro che la società non possa accettare alcuni secondi di incoerenza, quindi non dovresti nemmeno fare richieste sincrone.

Se hai bisogno di query point-in-time (che non ho notato nella tua domanda e quindi non ho incluso sopra, forse erroneamente), il costo per mantenerlo sui servizi a valle è così alto (dovresti duplicare logica di proiezione degli eventi interni in tutti i servizi a valle) che chiarisce la decisione: dovresti lasciare la proprietà ad A e richiedere una richiesta ad-hoc su web (o simile) e A dovrebbe usare il sourcing degli eventi per recuperare tutti gli eventi di cui sei a conoscenza al momento di proiettare allo stato e restituirlo. Immagino che questa possa essere l'opzione 2 (se ho capito bene?), Ma i costi sono tali che mentre l'accoppiamento temporale è migliore del costo di mantenimento degli eventi duplicati e della logica di proiezione.

Se non hai bisogno di un punto nel tempo, e non esiste un unico proprietario chiaro dei dati (che nella mia risposta iniziale l'ho assunto in base alla tua domanda), allora un modello molto ragionevole sarebbe quello di contenere le rappresentazioni del prodotto in ciascun servizio separatamente. Quando si aggiornano i dati per i prodotti, si aggiornano A, B e C in parallelo inviando richieste Web parallele a ciascuno di essi oppure si dispone di un'API di comando che invia più comandi a ciascuno di A, B e C. I B & C usano il loro versione locale dei dati per fare il loro lavoro, che può essere o meno stantio. Questa non è una delle opzioni sopra (anche se potrebbe essere fatta per essere vicina all'opzione 3), poiché i dati in A, B e C possono differire e il "tutto" del prodotto può essere una composizione di tutti e tre i dati fonti.

Sapere se la fonte della verità ha un contratto stabile è utile perché puoi usarlo per usare il dominio / eventi interni (o eventi che memorizzi nel tuo approvvigionamento di eventi come modello di archiviazione in A) per l'integrazione tra A e i servizi B e C. Se il contratto è stabile, è possibile integrarsi attraverso gli eventi del dominio. Tuttavia, hai un'ulteriore preoccupazione nel caso in cui le modifiche siano frequenti o che il contratto di messaggio sia sufficientemente ampio da rendere un problema il trasporto.

Se hai un proprietario chiaro, con un contratto che dovrebbe essere stabile, le migliori opzioni sarebbero l'opzione 1; un ordine conterrebbe tutte le informazioni necessarie e quindi B e C farebbero la loro funzione usando i dati nell'evento.

Se il contratto può cambiare o rompersi spesso, in seguito all'opzione 3, ricorrere alle richieste Web per recuperare i dati del prodotto è in realtà un'opzione migliore, poiché è molto più semplice mantenere più versioni. Quindi B farebbe una richiesta sulla v3 del prodotto.


Sì, sono d'accordo. Mentre ProductAddedo ProductDetailsChangedaggiungiamo complessità nel tenere traccia delle modifiche al catalogo dei prodotti, dobbiamo mantenere i dati sincronizzati tra i database in qualche modo, nel caso in cui gli eventi vengano riprodotti e dobbiamo accedere ai dati del catalogo dal passato.
Eithed

@eithed Ho aggiornato la risposta per espandere alcune ipotesi che ho formulato.
Savvas Kleanthous
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.