Una guida completa per scalare efficacemente le tue pipeline di dati e i prodotti dati con il Contract Testing e dbt

Una guida completa per ottimizzare le tue pipeline di dati e i prodotti dati utilizzando il Contract Testing e dbt

Tutto ciò che devi sapere per iniziare a implementare i test di contratto con dbt

Foto di Jonas Gerg su Unsplash

Lascia che ti racconti una storia sui sistemi di gestione dei dati e sulla scala che probabilmente ti colpirà se sei un data engineer o un analista dati che cerca di fare del suo meglio nel 2023.

Non molto tempo fa, quasi tutte le architetture dei dati e le strutture dei team di dati seguivano un approccio centralizzato. Come data engineer o analista dati, sapevi dove trovare tutta la logica di trasformazione e i modelli perché erano tutti nella stessa codebase. Probabilmente lavoravi a stretto contatto con il collega che costruiva il data pipeline che stavi utilizzando. C’era solo un team di dati, al massimo due.

Questo approccio era efficace per le piccole organizzazioni e le startup con fonti di dati e casi d’uso limitati. Funzionava anche per le grandi imprese non totalmente concentrate sull’estrazione di valore dai dati su larga scala. Tuttavia, all’aumentare della priorità delle organizzazioni di essere guidate dai dati, c’era una maggiore necessità di casi d’uso di machine learning, analisi e business intelligence.

Architettura centralizzata dei dati sviluppata e mantenuta da un singolo team

La proliferazione dei casi d’uso e delle fonti di dati ha aumentato la complessità nella gestione dei dati e il numero di persone necessarie per creare e mantenere i sistemi di dati. Per soddisfare queste esigenze, l’ultima versione della strategia dati della tua azienda potrebbe essersi spostata verso la decentralizzazione. Ciò include la formazione di team di dati decentralizzati e l’adozione di architetture dati decentralizzate come Data Mesh.

La decentralizzazione consente alle organizzazioni di scalare la gestione dei dati, ma comporta nuove sfide nell’assicurare il coordinamento di diversi componenti, come i prodotti e i data pipeline sviluppati e gestiti da vari team.

In questo tipo di architettura e strutture organizzative, diventa spesso poco chiaro chi è responsabile di ciascun componente, con conseguenti problemi e scaricamento delle responsabilità. Il numero di punti di integrazione tra i team aumenta e diventa più difficile mantenere interfacce funzionanti tra i diversi componenti.

Architettura dati decentralizzata con più team e più componenti

Se ti trovi in questa situazione, non sei solo. La tua organizzazione potrebbe essere in fase di decentralizzazione dei dati. Per affrontare questa transizione, possiamo prendere spunto dalle implementazioni di successo della decentralizzazione e delle architetture distribuite come i microservizi nel mondo operativo. Come hanno fatto? Come sono riusciti a fornire sistemi affidabili a quella scala? Beh, hanno sfruttato moderne tecniche di testing.

“Per anni, l’ingegneria del software ha abbracciato con successo il concetto di piccole unità di lavoro svolte da “team delle due pizze”. Ogni team possiede il proprio componente di un sistema più ampio. I team si integrano tra loro attraverso interfacce ben definite e versionate. Sfortunatamente, i dati devono ancora raggiungere il livello del software. L’architettura dati monolitica è ancora la norma, nonostante ci siano chiari svantaggi.” – dbt labs

In questo articolo, presenterò una di queste tecniche: il testing di contratto. Mostrerò come puoi utilizzare dbt per creare semplici test di contratto per le tue fonti upstream e le interfacce pubbliche dei tuoi modelli dbt. Questo tipo di test ti manterrà lucido mentre le tue app dbt diventano sempre più complesse e decentralizzate.

Ma… cos’è un test di contratto?

Quando un sistema distribuito inizia a crescere in più componenti sviluppate da diverse squadre, il primo approccio che le squadre possono provare a seguire per verificare che il sistema si comporti come previsto è implementare test end-to-end che eseguano il sistema nel suo complesso.

Scope dei test end-to-end concentrati sulla verifica dell'intero sistema

I test end-to-end diventano spesso molto difficili da gestire a causa della loro complessità, del feedback lento e di quanto siano difficili da mantenere e orchestrare.

Questo era il caso nel mondo operativo durante l’implementazione di microservizi su larga scala. Quando non è possibile testare il sistema nel suo complesso, le squadre di sviluppo hanno iniziato a implementare approcci diversi, come il test di contratto.

“Un test di contratto di integrazione è un test al confine di un servizio esterno che verifica che rispetti il contratto previsto da un servizio consumatore.” – Toby Clemson

Le squadre possono comunque mantenere un piccolo set di test end-to-end, ma scendono nella piramide dei test verificando il sistema con test più veloci e affidabili come i test di contratto, di componente e di unità.

I compromessi dei diversi tipi di test vengono spesso visualizzati con una piramide dei test. Ho menzionato questo concetto nel mio precedente articolo su implementazione dei test di unità per i modelli dbt.

Piramide dei test tipica per i sistemi operativi

Se applichiamo lo stesso concetto ai sistemi di gestione dei dati, i test di contratto per le app dbt possono essere implementati per verificare il comportamento di due tipi di interfacce:

  • Le fonti upstream.
  • Le interfacce pubbliche, come i marts e i porti di output.
Scope dei test di contratto

Benefici del test di contratto per i sistemi di dati

Come abbiamo visto, le architetture dei dati stanno diventando sempre più complesse e decentralizzate, come è successo una volta con i servizi operativi. Man mano che questo tipo di sistema continua a crescere, la capacità di eseguire suite di test end-to-end mantenibili ed efficaci diminuisce.

Il test di contratto diventa un potente alleato nella gestione di diverse situazioni fornendo diversi vantaggi:

  • Riduzione del numero di test end-to-end necessari per verificare il comportamento del sistema. Ciò porta a un feedback più rapido e a costi di manutenzione inferiori.
  • Gestire la complessità di avere diverse squadre di dati che lavorano nello stesso codice fornendo aspettative chiare tra le interfacce pubbliche delle squadre.
  • Evidenziare problemi di integrazione tra i componenti negli ambienti di test inferiori prima che raggiungano la produzione.
  • Interfacce meglio definite e documentate tra i diversi flussi di dati o prodotti di dati.

Test di contratto vs test di qualità dei dati

Potresti pensare, ma… il concetto di test di contratto sembra simile al test di qualità che stiamo già eseguendo nei nostri flussi di dati.

Questa è un’osservazione valida poiché c’è una linea sottile tra il scopo dei test di contratto e dei test di qualità dei dati. Mi piace pensare ai test di contratto come a un sottoinsieme dei test di qualità come parte di una moderna strategia di testing dei dati.

I test di contratto possono essere considerati un sottoinsieme dei test di qualità

La differenza è che un test di contratto guarda lo schema e i vincoli mentre il test di qualità dei dati guarda i dati effettivi e le loro caratteristiche. Vediamo alcuni esempi.

Scopo dei test di contratto:

  • Verifica dei tipi di colonne.
  • Verifica dei vincoli previsti a livello di schema come chiavi primarie e esterne, colonne non nulle.
  • Verifica dei valori accettati per una determinata colonna.
  • Verifica degli intervalli validi per una determinata colonna.

Scopo dei test di qualità:

  • Valutazione della completezza, ad esempio percentuale di non nulle in una colonna.
  • Valutazione dell’univocità, ad esempio numero di righe non univoche.
  • Valutazione della coerenza, ad esempio tutti gli identificatori utente nella sorgente sono inclusi nell’output.

Implementazione del nostro primo test di contratto

Okey, basta con la teoria, passiamo all’azione con un esempio semplice. Abbiamo un’app dbt chiamata health-insights che prende i dati di peso e altezza dalle fonti di dati upstream e calcola l’indice di massa corporea.

I nostri colleghi del fantastico team di backend sono responsabili della produzione dei dati di peso e altezza di cui abbiamo bisogno per costruire la nostra app health-insights. Lavorano in un team diverso che è un po’ impegnato e stressato. A volte non ci avvisano dei cambiamenti dello schema. Per testare questi cambiamenti nelle interfacce upstream, abbiamo deciso di creare il nostro primo test di contratto della sorgente.

The system architecture of our example

Prima di tutto, dobbiamo aggiungere due nuovi pacchetti dbt, dbt-expectations e dbt-utils, che ci consentiranno di fare affermazioni sullo schema delle nostre sorgenti e sui valori accettati.

# packages.ymlpackages:  - package: dbt-labs/dbt_utils    version: 1.1.1  - package: calogica/dbt_expectations    version: 0.8.5

Test delle fonti di dati

Iniziamo definendo un test di contratto per la nostra prima sorgente. Prendiamo dati da raw_height, una tabella che contiene informazioni sull’altezza degli utenti dell’app palestra.

Siamo d’accordo con i nostri produttori di dati che riceveremo la misurazione dell’altezza, le unità di misura e l’ID dell’utente. Siamo d’accordo sui tipi di dati e sul fatto che solo ‘cm’ e ‘pollici’ siano supportati come unità. Con tutto questo, possiamo definire il nostro primo contratto nel file YAML della fonte dbt.

I mattoni fondamentali

Guardando il test precedente, possiamo vedere che vengono utilizzate diverse macro di test dell’unità dbt:

  • dbt_expectations.expect_column_values_to_be_of_type: Questa affermazione ci consente di definire il tipo di dati della colonna previsto.
  • accepted_values: Questa affermazione ci consente di definire un elenco dei valori accettati per una colonna specifica.
  • dbt_utils.accepted_range: Questa affermazione ci consente di definire un intervallo numerico per una determinata colonna. Nell’esempio, ci aspettiamo che il valore della colonna non sia inferiore a 0.
  • not null: Infine, le affermazioni integrate come ‘not null’ ci consentono di definire i vincoli di colonna.

Utilizzando questi mattoni fondamentali, abbiamo aggiunto diversi test per definire le aspettative di contratto descritte sopra. Notare anche come abbiamo contrassegnato i test come “test-di-contratto-sorgente”. Questo tag ci consente di eseguire tutti i test di contratto in isolamento, sia in locale che, come vedremo in seguito, nel flusso di lavoro CI/CD:

dbt test --select tag:contract-test-source

Implementazione di test di contratto per marts e porte di output

Abbiamo visto quanto velocemente possiamo creare test di contratto per le fonti della nostra app dbt, ma cosa succede con le interfacce pubbliche del nostro data pipeline o del nostro prodotto dati?

Come produttori di dati, vogliamo assicurarci di produrre dati in base alle aspettative dei nostri consumatori di dati in modo da poter soddisfare il contratto che abbiamo con loro e rendere affidabile e affidabile il nostro data pipeline o il nostro prodotto dati.

Un modo semplice per assicurarci di essere in linea con i nostri obblighi verso i nostri consumatori di dati è aggiungere test di contratto per le nostre interfacce pubbliche.

Dbt ha recentemente lanciato una nuova funzionalità per i modelli SQL, i contratti di modello, che consentono di definire il contratto per un modello dbt. Mentre si costruisce il modello, dbt verifica che la trasformazione del modello produca un dataset conforme al suo contratto, altrimenti non riuscirà a costruirlo.

Vediamolo in azione. Il nostro mart, body_mass_indexes, produce una metrica BMI dai dati di peso e altezza che otteniamo dalle nostre fonti. Il contratto con il nostro fornitore stabilisce quanto segue:

  • Tipo di dato per ogni colonna.
  • Gli ID utente non possono essere nulli.
  • Gli ID utente sono sempre superiori a 0.

Definiamo il contratto del modello body_mass_indexes utilizzando i contratti dei modelli dbt:

I mattoni

Guardando il file di specifica del modello precedente, possiamo vedere diversi metadati che ci consentono di definire il contratto.

  • contract.enforced: Questa configurazione dice a dbt che vogliamo imporre il contratto ogni volta che viene eseguito il modello.
  • data_type: Questa affermazione ci permette di definire il tipo di colonna che ci aspettiamo di ottenere una volta eseguito il modello.
  • constraints: Infine, il blocco dei vincoli ci dà la possibilità di definire vincoli utili come il fatto che una colonna non può essere nulla, impostare chiavi primarie e espressioni personalizzate. Nell’esempio precedente abbiamo definito un vincolo per dire a dbt che l’user_id deve essere sempre maggiore di 0. Puoi vedere tutti i vincoli disponibili qui.

Una differenza tra i test di contratto che abbiamo definito per le nostre fonti e quelli definiti per i nostri mart o porte di output è quando i contratti vengono verificati e applicati.

I contratti del modello sono applicati quando il modello viene generato da dbt run, mentre i contratti basati sui test dbt vengono applicati quando vengono eseguiti i test dbt.

Se uno dei contratti del modello non viene soddisfatto, verrà visualizzato un errore quando si esegue ‘dbt run’ con dettagli specifici sul fallimento. Puoi vedere un esempio nell’output della console di esecuzione di dbt run seguente.

1 su 4 INIZIO modello tabella sql dbt_testing_example.stg_gym_app__height ........... [ESECUZIONE]2 su 4 INIZIO modello tabella sql dbt_testing_example.stg_gym_app__weight ........... [ESECUZIONE]2 su 4 OK creato modello tabella sql dbt_testing_example.stg_gym_app__weight ...... [SELECT 4 in 0.88s]1 su 4 OK creato modello tabella sql dbt_testing_example.stg_gym_app__height ...... [SELECT 4 in 0.92s]3 su 4 INIZIO modello tabella sql dbt_testing_example.int_weight_measurements_with_latest_height  [ESECUZIONE]3 su 4 OK creato modello tabella sql dbt_testing_example.int_weight_measurements_with_latest_height  [SELECT 4 in 0.96s]4 su 4 INIZIO modello tabella sql dbt_testing_example.body_mass_indexes ............. [ESECUZIONE]4 su 4 ERRORE creazione modello tabella sql dbt_testing_example.body_mass_indexes .... [ERRORE in 0.77s]Completato l'esecuzione di 4 modelli di tabelle in 0 ore 0 minuti e 6.28 secondi (6.28s). Completato con un errore e 0 avvisi:Errore del database nel modello body_mass_indexes (models/marts/body_mass_indexes.sql) Nuova riga per la relazione "body_mass_indexes__dbt_tmp" viola il vincolo di controllo "body_mass_indexes__dbt_tmp_user_id_check1" DETTAGLIO: La riga che causa il fallimento contiene (1, 2009-07-01, 82.5, null, null). Codice compilato in target/run/dbt_testing_example/models/marts/body_mass_indexes.sql

Esecuzione dei test di contratto nel flusso di lavoro

Fino ad ora abbiamo una suite di test di contratto potenti, ma come e quando li eseguiamo?

Possiamo eseguire i test di contratto in due tipi di flussi di lavoro.

  • Flussi di lavoro CI/CD
  • Flussi di lavoro dei dati

Ad esempio, è possibile eseguire i test di contratto delle fonti secondo uno schema in un flusso di lavoro CI/CD puntando alle fonti di dati disponibili negli ambienti inferiori come test o staging. È possibile impostare il flusso di lavoro in modo che fallisca ogni volta che il contratto non viene soddisfatto.

Queste fallimenti forniscono informazioni preziose sulle modifiche al contratto introdotte da altri team prima che queste modifiche arrivino in produzione.

Esempio di una pipeline CI/CD di dbt in Github Actions

Puoi anche eseguire i tuoi test del contratto dell’output/porta ogni volta che effettui una nuova modifica tramite la pipeline CI/CD. Poiché i contratti dei modelli dbt vengono verificati ogni volta che viene costruito il modello, dici a dbt di applicare il contratto in modo che se le nuove modifiche del modello introducono una modifica del contratto, il tuo team verrà avvisato prima che i tuoi consumatori di dati ne siano influenzati.

Inoltre, puoi anche eseguire i tuoi test del contratto dell’input e dell’output/porta nelle tue pipeline di dati in produzione. Eseguire i test del contratto in produzione può aiutare il tuo team a capire se una pipeline di dati è fallita perché una delle dipendenze a monte ha violato il contratto o perché i dati che stai producendo non soddisfano il contratto con i consumatori a valle.

Suggerimenti aggiuntivi per iniziare

  • Inizia con piccoli passi, testando i punti di integrazione più vulnerabili e inclini al fallimento.
  • Applica il modello del lettore tollerante durante l’implementazione dei test del contratto. Verifica solo i dati di cui hai bisogno.
  • Personalizza il comportamento del test del contratto in base alle tue esigenze, puoi configurare l’attributo di severità in modo che essi generino un errore evidente o che lancino solo un avviso.
  • Integra questi tipi di test con strumenti moderni di osservabilità dei dati come Montecarlo in modo che facciano parte del tuo processo di gestione degli incidenti.
  • Sfrutta i test del contratto di dbt anche se i tuoi sistemi di dati non sono sviluppati utilizzando dbt. Puoi comunque definire test del contratto di origine in dbt ed eseguirli su tabelle o file creati con altri framework o SQL semplice.
  • Considera tecniche avanzate di test del contratto come contratti orientati al consumatore che potrebbero rendere più facile l’implementazione del test del contratto in contesti specifici.

Conclusione

Abbiamo visto come le strategie di test per i sistemi di dati possono beneficiare anche delle tecniche di test del contratto mentre questi sistemi diventano sempre più decentralizzati e complessi.

Abbiamo anche visto come iniziare ad implementare test del contratto sfruttando le funzionalità integrate di dbt e i pacchetti aggiuntivi di dbt. Applichiamo questo tipo di test a due punti di integrazione: origini dei dati a monte e marts/porte di output dei dati.

Spero che questo articolo ti fornisca e al tuo team tutti gli strumenti e i suggerimenti per iniziare ad implementare test del contratto man mano che i tuoi sistemi di dati crescono per soddisfare nuovi casi d’uso dei dati. Se sei curioso, puoi controllare il codice sorgente dell’esempio dell’applicazione dbt in questo repository di Github.

Sei pronto a provare e iniziare il tuo percorso di test del contratto? Mi piacerebbe sentire i tuoi pensieri ed esperienze nei commenti.

Nel mio prossimo articolo, parlerò del pezzo mancante nella mia serie di test dei sistemi di dati, i test di qualità dei dati e come implementarli con dbt.

Grazie ai miei colleghi di Thoughtworks Arne e Manisha per aver dedicato del tempo a revisionare le prime versioni di questo articolo. Grazie agli sviluppatori del pacchetto dbt-expectations per il loro ottimo lavoro.

Tutte le immagini, se non diversamente indicato, sono dell’autore.