
In questo articolo spiegheremo in dettaglio come affrontare i problemi di compatibilità dei database durante il deployment. Ti racconteremo cosa può succedere alle tue applicazioni in produzione se provi a effettuare il deployment senza una preparazione adeguata. Successivamente, esamineremo le fasi del ciclo di vita dell'applicazione necessarie per garantire un tempo di inattività nullo (nota traduttore: di seguito — zero downtime). Il risultato delle nostre operazioni sarà l'applicazione di una modifica al database non retrocompatibile in modo retrocompatibile.
Se desideri esaminare gli esempi di codice dell'articolo, li troverai su .
Introduzione
Zero downtime deployment
Che cos'è questo misterioso zero downtime deployment? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.
Come raggiungerlo? Ci sono diversi modi, eccone uno:
- distribuisci la versione n. 1 del tuo servizio
- effettua la migrazione del database
- distribuisci la versione n. 2 del tuo servizio parallelamente alla versione n. 1
- non appena vedi che la versione n. 2 funziona correttamente, rimuovi la versione n. 1
- fatto!
Facile, vero? Purtroppo, non è così semplice, e lo esamineremo in dettaglio più avanti. Ora, controlliamo un altro processo di deployment piuttosto comune: il blue green deployment.
Hai mai sentito parlare di ? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на , dove lo descriviamo in modo più dettagliato. In breve, ricapitoliamo come effettuare un blue green deployment:
- assicurati di avere due copie del tuo codice di produzione (“blue” e “green”);
- dirottare tutto il traffico nell'ambiente blue, ovvero in modo che gli URL di produzione puntino lì;
- distribuire e testare tutte le modifiche dell'applicazione nell'ambiente green;
- cambiare gli URL dall'ambiente blue all'ambiente green.
Il blue green deployment è un approccio che consente di introdurre nuove funzionalità senza preoccuparsi di eventuali malfunzionamenti nella produzione. Questo è possibile perché, anche nel caso in cui si verifichi un problema, puoi facilmente tornare all'ambiente precedente semplicemente "facendo clic su un interruttore".
Dopo aver letto tutto quanto sopra, ti starai chiedendo: che relazione ha il zero downtime con il blue green deployment?
Beh, hanno molto in comune, poiché il supporto di due copie dello stesso ambiente comporta sforzi doppi per la loro manutenzione. Ecco perché alcune squadre, come afferma , adottano una variante di questo approccio:
un'altra opzione è quella di utilizzare lo stesso database, creando switch blu-verdi per i livelli web e domain. In questo approccio, i database possono spesso rappresentare un problema, soprattutto quando è necessario modificare il loro schema per supportare una nuova versione del software.
E qui arriviamo al problema principale di questo articolo. Database. Diamo un'altra occhiata a questa frase.
effettuare la migrazione del database.
Ora dovresti porti la domanda: cosa succede se la modifica del database non è retrocompatibile? La mia prima versione dell'applicazione non si romperà? In realtà, è proprio questo che accadrà…
Pertanto, anche se i vantaggi del zero downtime / blue green deployment sono enormi, le aziende tendono a seguire il processo di distribuzione delle loro applicazioni più sicuro:
- preparare il pacchetto con la nuova versione dell'applicazione
- spegnere l'applicazione in esecuzione
- eseguire gli script per la migrazione del database
- distribuire e avviare la nuova versione dell'applicazione
In questo articolo, descriveremo in dettaglio come puoi lavorare con il database e il codice per sfruttare i vantaggi del zero downtime deployment.
Problemi con il database
Se hai un'applicazione stateless che non memorizza dati in un database, puoi ottenere un deployment senza downtime immediato. Purtroppo, gran parte del software deve memorizzare i dati da qualche parte. Ecco perché è importante riflettere attentamente prima di apportare modifiche allo schema. Prima di approfondire come modificare lo schema in modo da consentire un deployment senza interruzioni, concentriamoci prima sul versioning dello schema.
Versioning dello schema
In questo articolo utilizzeremo come strumento per il versioning (nota dell'autore: si parla di migrazioni del database). Naturalmente, scriveremo anche un'applicazione Spring Boot che ha supporto integrato per Flyway e che eseguirà la migrazione dello schema durante l'inizializzazione del contesto dell'applicazione. Utilizzando Flyway, è possibile memorizzare gli script di migrazione nella cartella dei propri progetti (per impostazione predefinita in classpath:db/migration). Qui puoi vedere un esempio di tali file di migrazione
└── db
└── migration
├── V1__init.sql
├── V2__Add_surname.sql
├── V3__Final_migration.sql
└── V4__Remove_lastname.sqlIn questo esempio, vediamo 4 scenari di migrazione che, se non sono stati eseguiti in precedenza, verranno eseguiti uno dopo l'altro all'avvio dell'applicazione. Analizziamo uno dei file (V1__init.sql) come esempio.
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');Tutto parla da sé: puoi usare SQL per definire come dovrebbe essere modificata la tua base dati. Per ulteriori informazioni su Spring Boot e Flyway, consulta la .
Utilizzando uno strumento di gestione delle versioni con Spring Boot, ottieni 2 grandi vantaggi:
- separi le modifiche del database dalle modifiche al codice
- la migrazione del database avviene insieme al rilascio della tua applicazione, ovvero il tuo processo di deployment viene semplificato
Risoluzione dei problemi del database
Nella prossima sezione dell'articolo ci concentreremo sull'esame di due approcci alle modifiche del database.
- incompatibilità retroattiva
- compatibilità retroattiva
Il primo sarà considerato un avvertimento che non è consigliabile effettuare un deployment a zero downtime senza preparazione preliminare... Il secondo propone una soluzione su come eseguire il deployment senza interruzioni, mantenendo al contempo la retrocompatibilità.
Il nostro progetto, su cui lavoreremo, sarà una semplice applicazione Spring Boot Flyway, che contiene Persona con nome e cognome nel database (nota dell'autore: Persona è una tabella, e first_name e cognome è un campo al suo interno). Vogliamo rinominare cognome in cognome.
Assunzioni
Prima di approfondire i dettagli, è necessario definire un paio di assunzioni riguardanti le nostre applicazioni. L'obiettivo principale che vogliamo raggiungere sarà un processo piuttosto semplice.
Nota. Consiglio PROFESSIONALE: semplificare i processi può farti risparmiare molti soldi in manutenzione (più persone lavorano nella tua azienda, più soldi puoi risparmiare)!
Non è necessario eseguire il rollback del database
Questo semplifica il processo di deployment (alcuni rollback del database sono praticamente impossibili, come il rollback di una cancellazione). Preferiamo eseguire rollback solo delle applicazioni. In questo modo, anche se hai database diversi (come SQL e NoSQL), il tuo pipeline di deployment avrà un aspetto uniforme.
Deve esserci SEMPRE la possibilità di fare il rollback dell'applicazione a una versione precedente (non oltre).
Il rollback deve essere effettuato solo se necessario. Se nella versione attuale c'è un bug che è difficile da risolvere, dobbiamo avere la possibilità di tornare all'ultima versione funzionante. Supponiamo che questa ultima versione funzionante sia la precedente. Il supporto per la compatibilità del codice e del database per più di un deployment sarebbe estremamente difficile e costoso.
Nota. Per una maggiore leggibilità, in questo articolo cambieremo la versione maggiore dell'applicazione.
Passo 1: Stato iniziale
Versione dell'applicazione: 1.0.0
Versione del DB: v1
Commento
Questo sarà lo stato iniziale dell'applicazione.
Modifiche al DB
Il DB contiene last_name.
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');Modifiche al codice
L'applicazione salva i dati Person in cognome:
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastname) {
this.lastName = lastname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName
+ "]";
}
}Rinomina della colonna non retrocompatibile
Vediamo un esempio su come modificare il nome di una colonna:
Attenzione. Il seguente esempio porterà intenzionalmente a un malfunzionamento. Lo mostriamo per illustrare il problema di compatibilità del database.
Versione dell'applicazione: 2.0.0.BAD
Versione DB: v2bad
Commento
Le attuali modifiche NON ci permettono di eseguire due istanze (vecchia e nuova) simultaneamente. Pertanto, il deployment con zero downtime sarà difficile da raggiungere (considerando le assunzioni, è praticamente impossibile).
Test A/B
La situazione attuale è che abbiamo un'applicazione version 1.0.0, distribuita in produzione, e il DB v1. Dobbiamo distribuire una seconda istanza dell'applicazione, versione 2.0.0.BAD, e aggiornare il database a v2bad.
Passaggi:
- è stata distribuita una nuova istanza dell'applicazione versione
2.0.0.BAD, che aggiorna il database av2bad - nel database
v2badcolonnacognomenon esiste più — è stata modificata incognome - l'aggiornamento del database e dell'applicazione è avvenuto con successo, e alcune istanze funzionano in
1.0.0, altre in2.0.0.BAD. Tutti collegati al DBv2bad - tutte le istanze della versione
1.0.0inizieranno a generare errori, poiché tenteranno di inserire dati nella colonnacognome, che non esiste più - tutte le istanze della versione
2.0.0.BADfunzioneranno senza problemi
Come vedete, se facciamo modifiche incompatibili tra il DB e l'applicazione, il test A/B diventa impossibile.
Ripristino dell'applicazione
Supponiamo che, dopo aver tentato di eseguire il deployment A/B (nota: probabilmente qui l'autore si riferiva al test A/B) abbiamo deciso che dobbiamo ripristinare l'applicazione alla versione 1.0.0. Supponiamo che non vogliamo ripristinare il database.
Passaggi:
- fermiamo l'istanza dell'applicazione versione
2.0.0.BAD - il database è ancora
v2bad - poiché la versione
1.0.0non capisce cosa siacognome, vedremo errori - l'inferno è sfuggito, non possiamo più tornare indietro
Come vedete, se facciamo modifiche incompatibili tra il DB e l'applicazione, non possiamo tornare alla versione precedente.
Log di esecuzione dello script
Scenario non compatibile con le versioni precedenti:
01) Esegui 1.0.0
02) Aspetta che l'app (1.0.0) si avvii
03) Genera una persona chiamando POST localhost:9991/person per la versione 1.0.0
04) Esegui 2.0.0.BAD
05) Aspetta che l'app (2.0.0.BAD) si avvii
06) Genera una persona chiamando POST localhost:9991/person per la versione 1.0.0 <-- questo dovrebbe fallire
07) Genera una persona chiamando POST localhost:9992/person per la versione 2.0.0.BAD <-- questo dovrebbe avere successo
Avvio dell'app nella versione 1.0.0
Genera una persona nella versione 1.0.0
Invio un post a 127.0.0.1:9991/person. Questa è la risposta:
{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}
Avvio dell'app nella versione 2.0.0.BAD
Genera una persona nella versione 1.0.0
Invio un post a 127.0.0.1:9991/person. Questa è la risposta:
curl: (22) L'URL richiesto ha restituito errore: 500 Internal Server Error
Genera una persona nella versione 2.0.0.BAD
Invio un post a 127.0.0.1:9995/person. Questa è la risposta:
{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}Modifiche al DB
Script di migrazione che rinomina cognome in cognome
Script Flyway originale:
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');Script che rinomina cognome.
-- Questa modifica non è compatibile con le versioni precedenti - non puoi fare test A/B
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;Modifiche al codice
Abbiamo cambiato il nome del campo lastName con cognome.
Rinominazione della colonna in modo compatibile con le versioni precedenti
Questa è la situazione più comune con cui possiamo trovarci. Dobbiamo apportare modifiche incompatibili al contrario. Abbiamo già dimostrato che per un deployment senza interruzioni non dobbiamo semplicemente applicare la migrazione del database senza ulteriori azioni. In questa sezione dell'articolo effettueremo 3 deployment dell'applicazione insieme alle migrazioni del database per raggiungere il risultato desiderato e mantenere la retrocompatibilità.
Nota. Ricordiamo che abbiamo un database di versione
v1. Esso contiene le colonnenomeecognome. Dobbiamo modificarecognomeconcognome. Abbiamo anche un'applicazione di versione1.0.0,che al momento non utilizzacognome.
Passo 2: Aggiungiamo il cognome
Versione dell'applicazione: 2.0.0
Versione del DB: v2
Commento
Aggiungendo una nuova colonna e copiando il suo contenuto, creiamo modifiche retrocompatibili al database. Allo stesso tempo, se eseguiamo il rollback del JAR o abbiamo una vecchia versione funzionante del JAR, non si romperà durante l'esecuzione.
Distribuiamo la nuova versione
Passaggi:
- effettuate la migrazione del database per creare la nuova colonna
cognome. Ora il vostro database di versionev2 - copia i dati da
cognomeincognome. Attenzione, e se avete molti di questi dati, dovete considerare una migrazione in batch! - scrivete il codice dove sono utilizzati ENTRAMBI e nuovo, e vecchio colonna. Ora la vostra applicazione di versione
2.0.0 - leggi il valore dalla colonna
cognome, se non ènull, o da last_name, secognomenon è definito. Puoi rimuoveregetLastName()dal codice, poiché restituirànullse torni alla tua applicazione con3.0.0fino a2.0.0.
Se usi Spring Boot Flyway, questi due passaggi saranno eseguiti all'avvio della versione 2.0.0 dell'applicazione. Se stai eseguendo manualmente lo strumento di gestione della versione del database, dovrai fare due operazioni diverse (prima aggiorna manualmente la versione del db e poi distribuisci la nuova applicazione).
Importante. Ricorda che la colonna appena creata NON DEVE essere NOT NULL. Se ripristini, la vecchia applicazione non conosce la nuova colonna e non la imposterà durante
l'Inserimento.Ma se aggiungi questo vincolo e il tuo DB èv2, ci sarà bisogno di impostare un valore per la nuova colonna. Questo porterà a violazioni dei vincoli.Importante. Dovresti rimuovere il metodo
getLastName(), poiché nella versione3.0.0il codice non ha il concetto di colonnacognome. Ciò significa che verranno impostati null. Puoi mantenere il metodo e aggiungere controlli anull, ma una soluzione molto migliore sarebbe assicurarti che nella logicagetSurname()hai scelto il valore non nullo corretto.
Test A/B
La situazione attuale è che abbiamo un'applicazione version 1.0.0, distribuito in produzione, e il DB in v1. Dobbiamo distribuire una seconda istanza dell'applicazione versione 2.0.0, che aggiornerà il database a v2.
Passaggi:
- è stata distribuita una nuova istanza dell'applicazione versione
2.0.0, che aggiorna il database av2 - nel frattempo alcune richieste sono state elaborate dalle istanze di versione
1.0.0 - l'aggiornamento è andato a buon fine e hai diverse istanze funzionanti dell'applicazione versione
1.0.0e le altre versioni2.0.0.Tutti comunicano con il database inv2 - versione
1.0.0non utilizza nella base di dati la colonna surname, mentre la versione2.0.0la utilizza. Non si sovrappongono e non ci dovrebbero essere errori. - versione
2.0.0salva i dati sia nella vecchia che nella nuova colonna, garantendo la retrocompatibilità
Importante. Se hai delle query che contano gli elementi basandosi sui valori dalla vecchia / nuova colonna, devi ricordare che ora hai valori duplicati (è probabile che stiano ancora migrando). Ad esempio, se vuoi contare il numero di utenti la cui cognome (come si chiami la colonna) inizia con la lettera
A, fino a quando la migrazione dei dati non è completata (old→newcolonna) potresti avere dati inconsistenti se effettui una query sulla nuova colonna.
Ripristino dell'applicazione
Attualmente abbiamo l'applicazione versione 2.0.0 e il database in v2.
Passaggi:
- rollback della tua applicazione alla versione
1.0.0. - versione
1.0.0non utilizza nella base di dati la colonnacognome, quindi il rollback dovrebbe essere effettuato con successo
Modifiche al DB
Il DB contiene una colonna con il nome cognome.
Script Flyway originale:
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);
insert into PERSON (first_name, last_name) values ('Dave', 'Syer');Script di aggiunta cognome.
Attenzione. Ricorda che NON PUOI AGGIUNGERE alcuna restrizione NOT NULL alla colonna aggiunta. Se esegui il rollback di un JAR, la versione precedente non conoscerà la colonna aggiunta e imposterà automaticamente il suo valore su NULL. In presenza di tale restrizione, la vecchia applicazione smetterà semplicemente di funzionare.
-- NOTA: Questo campo non può avere la restrizione NOT NULL perché se esegui il rollback, la versione precedente non saprà di questo campo
-- e lo imposterà sempre su NULL
ALTER TABLE PERSON ADD surname varchar(255);
-- STIAMO PRESUPPONENDO CHE SIA UNA MIGRAZIONE VELOCE - ALTRIMENTI DOVREMMO MIGRARE A PEZZI
UPDATE PERSON SET PERSON.surname = PERSON.last_nameModifiche al codice
Salviamo i dati sia in cognome, sia in cognome. Leggiamo quindi da cognome, poiché questa colonna è la più rilevante. Durante il processo di deploy, alcune query potrebbero essere state elaborate da un'istanza dell'applicazione che non è ancora stata aggiornata.
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String surname;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
/**
* Reading from the new column if it's set. If not the from the old one.
*
* When migrating from version 1.0.0 -> 2.0.0 this can lead to a possibility that some data in
* the surname column is not up to date (during the migration process lastName could have been updated).
* In this case one can run yet another migration script after all applications have been deployed in the
* new version to ensure that the surname field is updated.
*
* However it makes sense since when looking at the migration from 2.0.0 -> 3.0.0. In 3.0.0 we no longer
* have a notion of lastName at all - so we don't update that column. If we rollback from 3.0.0 -> 2.0.0 if we
* would be reading from lastName, then we would have very old data (since not a single datum was inserted
* to lastName in version 3.0.0).
*/
public String getSurname() {
return this.surname != null ? this.surname : this.lastName;
}
/**
* Storing both FIRST_NAME and SURNAME entries
*/
public void setSurname(String surname) {
this.lastName = surname;
this.surname = surname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName + ", surname=" + this.surname
+ "]";
}
}Passo 3: Rimozione di last_name dal codice
Versione dell'applicazione: 3.0.0
Versione DB:v3
Commento
Nota del traduttore: A quanto pare, nell'articolo originale, l'autore ha erroneamente copiato il testo di questo blocco dal passo 2. In questo passo devono essere fatte modifiche al codice dell'applicazione per rimuovere la funzionalità che utilizza la colonna cognome.
Aggiungendo una nuova colonna e copiando il suo contenuto, abbiamo creato modifiche al database compatibili con le versioni precedenti. Inoltre, se ripristineremo un JAR o avremo un vecchio JAR funzionante, non si romperà durante l'esecuzione.
Ripristino dell'applicazione
Attualmente abbiamo un'app della versione 3.0.0 e un database v3. La versione 3.0.0 non salva i dati in cognome. Ciò significa che in cognome si trovano le informazioni più aggiornate.
Passaggi:
- rollback della tua applicazione alla versione
2.0.0. - versione
2.0.0utilizza ecognomeecognome. - versione
2.0.0prenderàcognome, se non è nullo, altrimenti —cognome
Modifiche al DB
Non ci sono modifiche strutturali nel database. Viene eseguito il seguente script, che esegue la migrazione finale dei dati vecchi:
-- ASSUMIAMO CHE SIA UNA MIGRAZIONE VELOCE - ALTRIMENTI DOVREMMO MIGRARE A BATCH
-- NON CONTROLLIAMO NEANCHE SE STIAMO SOVRASCRIVENDO INTESTAZIONI ESISTENTI. DOVREMMO CONFRONTARE
-- LE VERSIONI DELLE INTESTAZIONI PER ASSICURARCI CHE SE C'È GIÀ Un'INTESTAZIONE CON UN NUMERO DI VERSIONE MAGGIORE
-- NON LA SOVRASCRIVIAMO.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;
-- RIMOZIONE DEL VINCOLO NOT NULL; ALTRIMENTI CERCHERAI DI INSERIRE UN VALORE NULL PER LAST_NAME
-- CON UN VINCOLO NOT NULL.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;Modifiche al codice
Nota dell'autore: La descrizione di questo blocco è stata erroneamente copiata dall'autore dal passaggio 2. Secondo la logica del racconto dell'articolo, le modifiche nel codice in questo passaggio dovrebbero essere rivolte alla rimozione degli elementi che operano sulla colonna. cognome.
Memorizziamo i dati sia in cognome, sia in cognome. Inoltre, leggiamo dalla colonna cognome, poiché è la più pertinente. Durante il deployment, alcune richieste possono essere elaborate da un'istanza che non è stata ancora aggiornata.
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.flyway;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String surname;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSurname() {
return this.surname;
}
public void setSurname(String lastname) {
this.surname = lastname;
}
@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", surname=" + this.surname
+ "]";
}
}Passo 4: Rimozione di last_name dal DB
Versione dell'applicazione: 4.0.0
Versione del DB: v4
Commento
A causa del fatto che il codice della versione 3.0.0 non ha utilizzato la colonna cognome, durante l'esecuzione non succederà niente di negativo se ripristiniamo a 3.0.0 dopo la rimozione della colonna dal database.
Log di esecuzione dello script
Lo faremo nel seguente modo:
01) Esegui 1.0.0
02) Attendi che l'app (1.0.0) si avvii
03) Genera una persona chiamando POST localhost:9991/person per la versione 1.0.0
04) Esegui 2.0.0
05) Attendi che l'app (2.0.0) si avvii
06) Genera una persona chiamando POST localhost:9991/person per la versione 1.0.0
07) Genera una persona chiamando POST localhost:9992/person per la versione 2.0.0
08) Termina l'app (1.0.0)
09) Esegui 3.0.0
10) Attendi che l'app (3.0.0) si avvii
11) Genera una persona chiamando POST localhost:9992/person per la versione 2.0.0
12) Genera una persona chiamando POST localhost:9993/person per la versione 3.0.0
13) Termina l'app (3.0.0)
14) Esegui 4.0.0
15) Attendi che l'app (4.0.0) si avvii
16) Genera una persona chiamando POST localhost:9993/person per la versione 3.0.0
17) Genera una persona chiamando POST localhost:9994/person per la versione 4.0.0
Avvio dell'app nella versione 1.0.0
Genera una persona nella versione 1.0.0
Invio un post a 127.0.0.1:9991/person. Questa è la risposta:
{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}
Avvio dell'app nella versione 2.0.0
Genera una persona nella versione 1.0.0
Invio un post a 127.0.0.1:9991/person. Questa è la risposta:
{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}
Genera una persona nella versione 2.0.0
Invio un post a 127.0.0.1:9992/person. Questa è la risposta:
{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}
Termina l'app 1.0.0
Avvio dell'app nella versione 3.0.0
Genera una persona nella versione 2.0.0
Invio un post a 127.0.0.1:9992/person. Questa è la risposta:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}
Genera una persona nella versione 3.0.0
Invio un post a 127.0.0.1:9993/person. Questa è la risposta:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}
Termina l'app 2.0.0
Avvio dell'app nella versione 4.0.0
Genera una persona nella versione 3.0.0
Invio un post a 127.0.0.1:9993/person. Questa è la risposta:
{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}
Genera una persona nella versione 4.0.0
Invio un post a 127.0.0.1:9994/person. Questa è la risposta:
{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}Modifiche DB
Riguardo a v3 stiamo semplicemente rimuovendo la colonna cognome e aggiungendo i vincoli mancanti.
-- RIMUOVERE LA COLONNA
ALTER TABLE PERSON DROP last_name;
-- AGGIUNGERE VINCOLI
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;Modifiche al codice
Non ci sono modifiche al codice.
Risultato
Abbiamo ripristinato con successo una modifica incompatibile al nome della colonna, eseguendo diversi deployment retrocompatibili. Di seguito un riepilogo delle azioni svolte:
- deployment dell'applicazione versione
1.0.0conv1schema DB (nome colonna =cognome) - deployment dell'applicazione versione
2.0.0,che memorizza i dati incognomeecognome. L'applicazione legge dacognome. La DB è alla versionev2, contenente colonne comecognome, siasurname. surnameè una copia di last_name. (NOTA: questa colonna non deve avere vincolo not null) - deployment dell'applicazione versione
3.0.0, che memorizza i dati solo incognomee legge da surname. Per quanto riguarda la DB, si sta effettuando l'ultima migrazionecognomeincognome. Inoltre, il vincolo NOT NULL è stato rimosso dacognome. La DB è ora alla versionev3 - deployment dell'applicazione versione
4.0.0— non ci sono modifiche nel codice. Deployment del databasev4, che rimuovecognome. Qui puoi aggiungere eventuali vincoli mancanti nella DB.
Seguendo questo approccio, puoi sempre tornare a una versione precedente senza compromettere la compatibilità del database/applicazione.
Codice
Tutto il codice utilizzato in questo articolo è disponibile su . Di seguito è riportata una descrizione aggiuntiva.
Progetti
Dopo la clonazione del repository, vedrai la seguente struttura delle cartelle.
├── boot-flyway-v1 - versione 1.0.0 dell'app con lo schema v1
├── boot-flyway-v2 - versione 2.0.0 dell'app con lo schema v2 (compatibile con le versioni precedenti - l'app può essere ripristinata)
├── boot-flyway-v2-bad - versione 2.0.0.BAD dell'app con lo schema v2bad (non compatibile con versioni precedenti - l'app non può essere ripristinata)
├── boot-flyway-v3 - versione 3.0.0 dell'app con lo schema v3 (l'app può essere ripristinata)
└── boot-flyway-v4 - versione 4.0.0 dell'app con lo schema v4 (l'app può essere ripristinata)Script
Puoi eseguire gli script elencati di seguito, che dimostreranno modifiche compatibili e non compatibili con la retrocompatibilità nel DB.
Per vedere il caso di modifiche retrocompatibili, esegui:
./scripts/scenario_backward_compatible.shE per vedere il caso di modifiche non retrocompatibili, esegui:
./scripts/scenario_backward_incompatible.shSpring Boot Sample Flyway
Tutti gli esempi sono tratti da Spring Boot Sample Flyway.
Puoi dare un'occhiata a http://localhost:8080/flyway, dove c'è un elenco di script.
Questo esempio include anche la console H2 (all'indirizzo http://localhost:8080/h2-console), così puoi visualizzare lo stato del database (l'URL JDBC predefinito è - jdbc:h2:mem:testdb).
Inoltre
Leggi anche altri articoli nel nostro blog:
Fonte: habr.com
