Non è mai successo, ed ecco di nuovo!
In un progetto recente, abbiamo deciso di utilizzare Liquibase sin dall'inizio per evitare problemi in futuro. Come si è rivelato, non tutti i membri più giovani del team sanno usarlo correttamente. Ho condotto un workshop interno che ho poi deciso di trasformare in un articolo.
L'articolo include consigli utili e una descrizione delle tre trappole più evidenti in cui ci si può imbattere lavorando con strumenti di migrazione dei database relazionali, in particolare Liquibase. È destinato a programmatori Java di livello Junior e Middle, mentre per programmatori più esperti potrebbe essere interessante per strutturare e ripassare ciò che probabilmente è già noto.

Liquibase e Flyway sono le principali tecnologie concorrenti per la gestione delle versioni delle strutture relazionali nel mondo Java. Il primo è completamente gratuito e, nella pratica, è spesso scelto per l'uso, quindi Liquibase è stato scelto come protagonista della pubblicazione. Tuttavia, alcune delle pratiche descritte possono essere universali, a seconda dell'architettura della vostra applicazione.
Le migrazioni delle strutture relazionali sono un metodo nato per affrontare la scarsa flessibilità dei dati relazionali. Nell'era della moda per la programmazione orientata agli oggetti, il modo di lavorare con i database implicava che descrivessimo una volta per tutte lo schema e non lo tocchiamo più. Ma la realtà è che tutto cambia, e le modifiche nella struttura delle tabelle sono abbastanza frequenti. Naturalmente, il processo stesso può essere doloroso e sgradevole.
Non scenderò nei dettagli della tecnologia e delle istruzioni per aggiungere la libreria al proprio progetto, su questo argomento sono stati scritti abbastanza articoli:
Inoltre, c'era già un ottimo articolo sui consigli utili:
Consigli
Voglio condividere i miei consigli e commenti, che sono nati attraverso sudore, sangue e dolori nella risoluzione dei problemi di migrazione.
1. Prima di iniziare, dovresti familiarizzare con la sezione delle migliori pratiche su Liquibase
Sono descritte cose semplici, ma molto importanti, senza le quali l'uso della libreria può complicarvi la vita. Ad esempio, un approccio non strutturato alla gestione dei changset porterà prima o poi a confusione e migrazioni rotte. Se si distribuiscono modifiche alla struttura del database e alla logica dei servizi che dipendono l'una dall'altra in momenti diversi, c'è una grande probabilità che questo porti a test falliti o a un ambiente non funzionante. Inoltre, le raccomandazioni su come utilizzare Liquibase sul sito ufficiale contengono un punto riguardo alla scrittura e verifica degli script di rollback insieme agli script di migrazione principali. E nell'articolo ci sono esempi di codice riguardanti le migrazioni e il meccanismo di rollback.
2. Se inizi a usare strumenti di migrazione, non permettere modifiche manuali nella struttura del database.
Come si suol dire: «Una volta Persil, sempre Persil». Se il database della tua applicazione è iniziato a essere gestito tramite Liquibase, ogni modifica manuale porta immediatamente a uno stato inconsistente, e il livello di fiducia nei changset diventa zero. I rischi potenziali sono diverse ore spese per ripristinare il database, nella peggiore delle ipotesi, un server perso. Se nella tua squadra c'è un DBA architect "di vecchio stampo", spiega pazientemente e attentamente come andrà male se decide di modificare il database a suo piacimento da un presunto SQL Developer.
3. Se un changset è già stato pushato nel repository, evita di modificare.
Se un altro sviluppatore ha fatto pull e ha applicato un changset che sarà successivamente modificato, sicuramente parlerà bene di te quando riceverà un errore all'avvio dell'applicazione. Se la modifica di un changset dovesse in qualche modo trapelare nel dev, dovrai seguire una difficile strada di hotfix. Il nocciolo del problema riguarda la validazione delle modifiche tramite hash – il meccanismo principale di Liquibase. Quando si modifica il codice di un changset, cambia anche l'hash. La modifica dei changset è possibile solo se esiste la possibilità di ripristinare l'intero database da zero senza perdita di dati. In tal caso, il refactoring del codice SQL o XML può, al contrario, semplificare la vita e rendere le migrazioni più leggibili. Un esempio potrebbe essere la situazione in cui, all'inizio dell'applicazione, lo schema del database originale è stato concordato all'interno del team.
4. Avere backup verificati del database, se possibile
Qui, penso sia chiaro. Nel caso in cui la migrazione non vada a buon fine, tutto può essere ripristinato. In Liquibase c'è uno strumento di rollback, ma gli script per il rollback vengono scritti dallo sviluppatore stesso, e potrebbero esserci problemi con la stessa probabilità degli script del changelog principale. Questo significa che è utile prendere precauzioni con i backup in ogni caso.
5. Utilizza backup del database verificati nello sviluppo, se possibile
Se ciò non contraddice i contratti e la privacy, il database non contiene dati personali e non pesa come due soli — prima di applicare la migrazione sui server live, è possibile testare come funziona sulla macchina dello sviluppatore e identificare quasi il 100% dei potenziali problemi durante la migrazione.
6. Comunica con gli altri sviluppatori del team
In un processo di sviluppo ben organizzato, tutti nel team sanno chi sta lavorando a cosa. Nella realtà spesso non è così, quindi, se stai preparando modifiche alla struttura del database nel contesto del tuo compito, è consigliabile informare ulteriormente l'intero team. Se qualcuno sta facendo modifiche in parallelo, dovresti organizzarti con attenzione. È utile comunicare con i colleghi anche al termine del lavoro, non solo all'inizio. Molti problemi potenziali con i changelog possono essere risolti nella fase di code review.
7. Pensa a quello che fai!
Sembrerebbe un consiglio ovvio, applicabile a qualsiasi situazione. Tuttavia, molti problemi potrebbero essere evitati se lo sviluppatore analizzasse ancora una volta cosa sta facendo e quali potrebbero essere le conseguenze. Lavorare con le migrazioni richiede sempre maggiore attenzione e precisione.
Trappole
Ora esaminiamo alcune trappole comuni in cui è possibile cadere se non si seguono i consigli sopra, e cosa fare realmente?
Situazione 1. Due sviluppatori cercano di aggiungere nuovi changelog contemporaneamente

Vasia e Petja vogliono creare un changelog della versione 4, senza sapere l'uno dell'altro. Hanno effettuato modifiche nella struttura del database e hanno presentato una pull request, con file changelog diversi. Di seguito è proposto il seguente meccanismo di azione:
Come risolvere
- In qualche modo, i colleghi devono concordare in quale ordine devono andare i loro changelog, ad esempio, quello di Petja deve essere applicato per primo.
- Qualcuno deve integrare il secondo e contrassegnare il changeSet di Vasia con la versione 5. Questo può essere fatto tramite Cherry Pick o una fusione accurata.
- Dopo le modifiche, è fondamentale verificare la validità delle azioni effettuate.
In realtà, i meccanismi di Liquibase consentiranno di avere nel repository due changeSet della versione 4, quindi puoi lasciare tutto com'è. In altre parole, avrai semplicemente due modifiche della versione 4 con nomi diversi. Con questo approccio, diventa molto difficile orientarsi successivamente nelle versioni del database.
Inoltre, Liquibase, come la casa degli hobbit, custodisce molti segreti. Uno di questi è la chiave validCheckSum, che è apparsa dalla versione 1.7 e consente di specificare un valore di checksum valido per un determinato changeSet, indipendentemente da ciò che è memorizzato nel database. Documentazione afferma quanto segue:
Aggiungi un checksum considerato valido per questo changeSet, indipendentemente da ciò che è memorizzato nel database. Utilizzato principalmente quando è necessario modificare un changeSet e non si desidera che vengano generati errori nei database su cui è già stato eseguito (procedure non raccomandate)
Sì, questa procedura non è raccomandata. Ma a volte un potente mago luminoso padroneggia anche tecniche oscure.
Situazione 2. Migrazione che dipende dai dati

Supponiamo che non hai la possibilità di utilizzare backup database dai server attivi. Petya ha creato un changeSet, l'ha testato localmente e con piena fiducia nella sua correttezza ha effettuato una pull request in sviluppo. Il leader del progetto ha verificato, ha chiesto se Petya lo avesse controllato, e successivamente ha fuso. Ma il deployment sul server di sviluppo è fallito.
In realtà, è possibile, e nessuno è al sicuro da ciò. Accade quando le modifiche alla struttura delle tabelle sono in qualche modo legate a dati specifici nel database. È ovvio che se il database di Petya è riempito solo con dati di test, potrebbe non coprire tutti i casi problematici. Ad esempio, durante la cancellazione di una tabella si scopre che ci sono record in altre tabelle tramite Foreign Key, collegati ai record nella tabella eliminata. Oppure, modificando il tipo di colonna, si scopre che non il 100% dei dati può essere convertito al nuovo tipo.
Come risolvere
- Scrivere script speciali che verranno applicati una sola volta insieme alla migrazione e porteranno i dati nel formato corretto. Questo è un modo generale per risolvere il problema del trasferimento dei dati nelle nuove strutture dopo aver applicato le migrazioni, ma qualcosa di simile può essere applicato anche prima, in casi specifici. Questo approccio, ovviamente, non è sempre disponibile, poiché modificare i dati sui server attivi può essere pericoloso e persino dannoso.
- Un altro approccio complesso è modificare il changset esistente. La difficoltà sta nel fatto che tutte le DB, dove è stato già applicato nella sua forma attuale, dovranno essere ripristinati. È del tutto possibile che l'intero team backend sarà costretto a installare localmente il DB da zero.
- E il modo più universale è trasferire il problema dei dati nell'ambiente dello sviluppatore ricreando la stessa situazione e aggiungendo un nuovo changset, fino a quello rotto, che permetterà di aggirare il problema.

In generale, più il database per composizione dei dati è simile al database del server di produzione, minore sarà la possibilità che i problemi con le migrazioni si verifichino ampiamente. E, naturalmente, prima di inviare il changset nel repository, vale la pena riflettere alcune volte se questo romperà qualcosa.
Situazione 3. Liquibase inizia ad essere applicato dopo l'uscita in produzione
Supponiamo che il team leader abbia chiesto a Petya di integrare Liquibase nel progetto, tuttavia il progetto è già in produzione e c'è già una struttura del database esistente.
Di conseguenza, il problema consiste nel fatto che sui nuovi server o macchine degli sviluppatori, i dati delle tabelle devono essere ricreati da zero, mentre l'ambiente esistente deve rimanere in uno stato consistente, pronto per ricevere nuovi changset.
Come risolvere
Qui ci sono anche diversi percorsi:
- Il primo e più ovvio è avere uno script separato che deve essere applicato manualmente all'inizializzazione di un nuovo ambiente.
- Il secondo, meno ovvio, è avere una migrazione Liquibase che si trova in un altro Contesto Liquibase e applicarla. Maggiori dettagli sul Contesto Liquibase possono essere letti qui: . In generale, questo è un meccanismo interessante che può essere applicato con successo, ad esempio, per il testing.
- Il terzo percorso consiste in diversi passaggi. Innanzitutto deve essere creata una migrazione per le tabelle già esistenti. Successivamente deve essere applicata in un certo ambiente e in questo modo si otterrà il suo hash. Il passaggio successivo consiste nell'inizializzare nel nostro server non vuoto tabelle Liquibase vuote e nella tabella con la storia di applicazione dei cambiamenti si può inserire manualmente una registrazione di un cambiamento "applicato" con le modifiche già presenti nel database. In questo modo, nella storia del server esistente si partirà dalla versione 2 e tutti i nuovi ambienti si comporteranno in modo identico.

Situazione 4. Le migrazioni diventano enormi e non riescono a essere completate.
All'inizio dello sviluppo del servizio, di solito, Liquibase viene utilizzato come dipendenza esterna e tutte le migrazioni vengono elaborate all'avvio dell'applicazione. Tuttavia, col tempo, è possibile imbattersi nei seguenti casi:
- Le migrazioni diventano enormi e richiedono molto tempo per essere eseguite.
- Sorge la necessità di migrazioni in ambienti distribuiti, ad esempio su più istanze di server DB contemporaneamente.
In tal caso, l'applicazione delle migrazioni richiede troppo tempo, portando a un timeout all'avvio dell'applicazione. Inoltre, applicare le migrazioni per ogni istanza dell'applicazione separatamente potrebbe portare a una desincronizzazione tra i diversi server.
Come risolvere
In questi casi, il tuo progetto è già grande, forse anche maturo, e Liquibase inizia a fungere da strumento esterno separato. Infatti, Liquibase come libreria viene assemblata in un file jar e può funzionare sia come dipendenza all'interno di un progetto sia in modo autonomo.
In modalità autonoma, puoi delegare l'applicazione delle migrazioni al tuo ambiente CI/CD o sulle spalle forti dei tuoi amministratori di sistema o specialisti del deployment. Per questo sarà necessaria la linea di comando di Liquibase. . In questa modalità diventa possibile avviare l'applicazione solo dopo che tutte le migrazioni necessarie sono state completate.
Conclusione
In realtà, ci sono molte più insidie nella gestione delle migrazioni del database, e molte di esse richiedono un approccio creativo. È importante capire che, se utilizzato correttamente, questo strumento permette di evitare la maggior parte di queste insidie. Personalmente, mi sono trovato a dover affrontare tutti i problemi menzionati in vari modi, e alcuni di essi sono stati il risultato dei miei errori. Questo accade generalmente per distrazione, ma a volte anche a causa di una incompetenza tecnica nell'uso dello strumento.
Fonte: habr.com


