Distribuzione e database con tempi di inattività pari a zero

Distribuzione e database con tempi di inattività pari a zero

Questo articolo spiega in dettaglio come risolvere i problemi di compatibilità del database durante la distribuzione. Ti diremo cosa può accadere alle tue applicazioni di produzione se provi a distribuirle senza una preparazione preliminare. Esamineremo quindi le fasi del ciclo di vita dell'applicazione necessarie per avere tempi di inattività pari a zero (ca. corsia: ulteriore - zero tempi di inattività). Il risultato delle nostre operazioni sarà quello di applicare la modifica del database incompatibile con le versioni precedenti in modo compatibile con le versioni precedenti.

Se vuoi comprendere gli esempi di codice dell'articolo, puoi trovarli su GitHub.

Introduzione

Distribuzione con tempi di inattività pari a zero

Che mistico distribuzione con tempi di inattività pari a zero? Si può dire che questo avviene quando la tua applicazione viene distribuita in modo tale da poter introdurre con successo una nuova versione dell'applicazione in produzione, mentre l'utente non si accorge della sua indisponibilità. Dal punto di vista dell'utente e dell'azienda, questo è il miglior scenario di distribuzione possibile perché consente di introdurre nuove funzionalità e correggere i bug senza interruzioni.

Come raggiungere questo obiettivo? Esistono diversi modi, eccone uno:

  • distribuisci la versione n. 1 del tuo servizio
  • eseguire una 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 come dovrebbe, rimuovi la versione n. 1
  • pronto!

Facile, non è vero? Purtroppo non è così semplice e lo vedremo in dettaglio più avanti. Ora controlliamo un altro processo di distribuzione abbastanza comune: la distribuzione blu-verde.

Hai mai sentito parlare di schieramento verde blu? Cloud Foundry rende tutto questo estremamente semplice. Basta guardare questo articolo, dove lo descriviamo in modo più dettagliato. Per riassumere brevemente, ricordiamo come eseguire la distribuzione blu-verde:

  • assicurati che due copie del tuo codice di produzione (“blu” e “verde”) funzionino;
  • dirigere tutto il traffico verso l'ambiente blu, vale a dire in modo che gli URL di produzione puntino lì;
  • distribuire e testare tutte le modifiche dell'applicazione in un ambiente verde;
  • cambia gli URL dall'ambiente blu a quello verde

La distribuzione blu-verde è un approccio che consente di introdurre facilmente nuove funzionalità senza preoccuparsi di interruzioni della produzione. Ciò è dovuto al fatto che, anche se succede qualcosa, puoi facilmente tornare all'ambiente precedente semplicemente "premendo un interruttore".

Dopo aver letto tutto quanto sopra, potresti porre la domanda: cosa hanno a che fare i tempi di inattività pari a zero con l'implementazione Bluegreen?

Ebbene, hanno molto in comune, poiché mantenere due copie dello stesso ambiente richiede il doppio dello sforzo per mantenerle. Questo è il motivo per cui alcune squadre sostengono Martin Fowler, seguire una variante di questo approccio:

Un'altra opzione è utilizzare lo stesso database, creando interruttori blu-verdi per i livelli web e dominio. In questo approccio, il database può spesso rappresentare un problema, soprattutto quando è necessario modificarne lo schema per supportare una nuova versione del software.

E qui arriviamo al problema principale di questo articolo. База данных. Diamo un'altra occhiata a questa frase.

eseguire una migrazione del database.

Ora devi porsi la domanda: cosa succede se la modifica del database non è compatibile con le versioni precedenti? La mia prima versione dell'app non si romperà? In effetti, questo è esattamente ciò che accadrà...

Pertanto, nonostante gli enormi vantaggi derivanti dall'assenza di tempi di inattività/implementazione blu-verde, le aziende tendono a seguire il seguente processo più sicuro per l'implementazione delle proprie applicazioni:

  • preparare un pacchetto con una nuova versione dell'applicazione
  • chiudere un'applicazione in esecuzione
  • eseguire script per migrare il database
  • distribuire e avviare una nuova versione dell'applicazione

In questo articolo spiegheremo in dettaglio come lavorare con il database e il codice per sfruttare i vantaggi di una distribuzione senza tempi di inattività.

Problemi di database

Se disponi di un'applicazione stateless che non archivia dati nel database, puoi ottenere subito una distribuzione senza tempi di inattività. Sfortunatamente, la maggior parte dei software deve archiviare i dati da qualche parte. Questo è il motivo per cui dovresti pensarci due volte prima di apportare qualsiasi modifica al circuito. Prima di entrare nei dettagli su come modificare lo schema in modo da rendere possibile una distribuzione senza tempi di inattività, concentriamoci innanzitutto sullo schema di controllo delle versioni.

Schema di versione

In questo articolo useremo flyway come strumento di controllo della versione (ca. Traduzione: stiamo parlando di migrazioni di database). Naturalmente, scriveremo anche un'applicazione Spring Boot con supporto Flyway integrato ed eseguiremo la migrazione dello schema durante la configurazione del contesto dell'applicazione. Quando utilizzi Flyway, puoi archiviare gli script di migrazione nella cartella dei 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.sql

In questo esempio vediamo 4 script di migrazione che, se non eseguiti precedentemente, verranno eseguiti uno dopo l'altro all'avvio dell'applicazione. Diamo un'occhiata a 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 è perfettamente autoesplicativo: puoi usare SQL per definire come deve essere modificato il tuo database. Per ulteriori informazioni su Spring Boot e Flyway, consulta Documenti di avvio primaverile.

Utilizzando uno strumento di controllo del codice sorgente con Spring Boot, ottieni 2 grandi vantaggi:

  • separi le modifiche al database dalle modifiche al codice
  • La migrazione del database avviene insieme al lancio della tua applicazione, ad es. il processo di distribuzione è semplificato

Risoluzione dei problemi relativi al database

Nella sezione successiva dell'articolo ci concentreremo sull'esame di due approcci alle modifiche del database.

  • incompatibilità con le versioni precedenti
  • retrocompatibilità

Il primo verrà considerato come un avvertimento che non dovresti eseguire una distribuzione senza tempi di inattività senza una preparazione preliminare... Il secondo offre una soluzione su come eseguire una distribuzione senza tempi di inattività e allo stesso tempo mantenere la compatibilità con le versioni precedenti.

Il nostro progetto su cui lavoreremo sarà una semplice applicazione Spring Boot Flyway con Person с first_name и last_name nella banca dati (ca. traduzione: Person è una tabella e first_name и last_name - questi sono i campi al suo interno). Vogliamo rinominare last_name в surname.

Presupposti

Prima di entrare nei dettagli, ci sono un paio di presupposti che dobbiamo fare sulle nostre applicazioni. Il risultato principale che vogliamo ottenere sarà un processo abbastanza semplice.

La nota. SUGGERIMENTO PRO per le imprese. Semplificare i processi può farti risparmiare un sacco di soldi sul supporto (più persone lavori per la tua azienda, più soldi puoi risparmiare)!

Non è necessario eseguire il rollback del database

Ciò semplifica il processo di distribuzione (alcuni rollback del database sono quasi impossibili, come il rollback dell'eliminazione). Preferiamo eseguire il rollback solo delle applicazioni. In questo modo, anche se disponi di database diversi (ad esempio SQL e NoSQL), la pipeline di distribuzione avrà lo stesso aspetto.

Deve SEMPRE essere possibile eseguire il rollback dell'applicazione di una versione precedente (non di più)

Il rollback dovrebbe essere eseguito solo quando necessario. Se nella versione corrente è presente un bug che non può essere risolto facilmente, dovremmo essere in grado di ripristinare l'ultima versione funzionante. Partiamo dal presupposto che quest'ultima versione funzionante sia la precedente. Mantenere la compatibilità del codice e del database per più di un'implementazione sarebbe estremamente difficile e costoso.

Заметка. Per una maggiore leggibilità, in questo articolo cambieremo la versione principale dell'applicazione.

Passaggio 1: stato iniziale

Versione dell'applicazione: 1.0.0
Versione DB: v1

commento

Questo sarà lo stato iniziale dell'applicazione.

Modifiche alla banca dati

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 memorizza i dati personali in last_name:

/*
 * 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
                + "]";
    }
}

Ridenominazione delle colonne incompatibile con le versioni precedenti

Diamo un'occhiata a un esempio di come modificare il nome di una colonna:

Attenzione. Il seguente esempio romperà intenzionalmente le cose. Lo mostriamo per dimostrare il problema della compatibilità del database.

Versione dell'applicazione: 2.0.0.BAD

Versione DB: v2bad

commento

Le modifiche attuali NON ci consentono di eseguire due istanze (vecchia e nuova) contemporaneamente. Pertanto, sarà difficile ottenere un’implementazione con tempi di inattività pari a zero (se si prendono in considerazione le ipotesi, in realtà è impossibile).

Test A/B

La situazione attuale è che abbiamo una versione dell'applicazione 1.0.0, distribuito in produzione e database v1. Dobbiamo distribuire una seconda istanza dell'applicazione, versione 2.0.0.BADe aggiornare il database a v2bad.

passaggi:

  1. viene distribuita una nuova istanza dell'applicazione versione 2.0.0.BADche aggiorna il database v2bad
  2. nel database v2bad colonna last_name non esiste più: è stato cambiato in surname
  3. L'aggiornamento del database e dell'applicazione ha avuto esito positivo e alcune istanze sono in esecuzione 1.0.0, altri - dentro 2.0.0.BAD. Tutto è collegato al database v2bad
  4. tutte le istanze della versione 1.0.0 inizierà a generare errori perché proverà a inserire dati nella colonna last_namechi non esiste più
  5. tutte le istanze della versione 2.0.0.BAD funzionerà senza problemi

Come puoi vedere, se apportiamo modifiche retrocompatibili al database e all'applicazione, il test A/B è impossibile.

Rollback dell'applicazione

Supponiamo che dopo aver provato a eseguire la distribuzione A/B (ca. per.: probabilmente qui l'autore intendeva test A/B) abbiamo deciso che dovevamo ripristinare la versione dell'applicazione 1.0.0. Diciamo che non vogliamo eseguire il rollback del database.

passaggi:

  1. fermiamo l'istanza dell'applicazione della versione 2.0.0.BAD
  2. il database è fermo v2bad
  3. dalla versione 1.0.0 non capisce cosa sia surname, vedremo degli errori
  4. Si è scatenato l'inferno, non possiamo più tornare indietro

Come puoi vedere, se apportiamo modifiche incompatibili con le versioni precedenti al database e all'applicazione, non possiamo ripristinare la versione precedente.

Registri di esecuzione degli script

Backward incompatible scenario:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0.BAD
05) Wait for the app (2.0.0.BAD) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0 <-- this should fail
07) Generate a person by calling POST localhost:9992/person to version 2.0.0.BAD <-- this should pass

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}

Starting app in version 2.0.0.BAD
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

curl: (22) The requested URL returned error: 500 Internal Server Error

Generate a person in version 2.0.0.BAD
Sending a post to 127.0.0.1:9995/person. This is the response:

{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}

Modifiche alla banca dati

Script di migrazione che rinomina last_name в surname

Fonte script Flyway:

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 last_name.

-- This change is backward incompatible - you can't do A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Modifiche al codice

Abbiamo cambiato il nome del campo lastName su surname.

Rinominare una colonna in modo compatibile con le versioni precedenti

Questa è la situazione più comune che possiamo incontrare. Dobbiamo apportare modifiche incompatibili con le versioni precedenti. Abbiamo già dimostrato che per un'implementazione senza tempi di inattività non dovremmo semplicemente applicare la migrazione del database senza passaggi aggiuntivi. In questa sezione dell'articolo, eseguiremo 3 distribuzioni dell'applicazione insieme alle migrazioni del database per ottenere il risultato desiderato mantenendo la compatibilità con le versioni precedenti.

La nota. Ricordiamo che abbiamo un database delle versioni v1. Contiene colonne first_name и last_name. Dobbiamo cambiare last_name su surname. Abbiamo anche la versione dell'app 1.0.0, che non è ancora utilizzato surname.

Passaggio 2: aggiungi il cognome

Versione dell'applicazione: 2.0.0
Versione DB: v2

commento

Aggiungendo una nuova colonna e copiandone il contenuto, creiamo modifiche al database compatibili con le versioni precedenti. Allo stesso tempo, se eseguiamo il rollback del JAR o ne abbiamo uno vecchio in esecuzione, non si romperà durante l'esecuzione.

Stiamo lanciando una nuova versione

passaggi:

  1. eseguire una migrazione del database per creare una nuova colonna surname. Ora la tua versione DB v2
  2. copiare i dati da last_name в surname. Notache se disponi di molti di questi dati, dovresti considerare la migrazione batch!
  3. scrivere il codice dove vengono utilizzati ENTRAMBI и nuovoE старый colonna. Ora la versione della tua app 2.0.0
  4. leggere il valore dalla colonna surname, se non è null, o da last_namese surname non specificato. Puoi eliminare getLastName() dal codice, poiché verrà generato null quando si ripristina l'applicazione da 3.0.0 a 2.0.0.

Se utilizzi Spring Boot Flyway, questi due passaggi verranno eseguiti durante l'avvio della versione 2.0.0 applicazioni. Se esegui manualmente lo strumento di controllo delle versioni del database, dovrai fare due cose diverse per farlo (prima aggiornare manualmente la versione del database e quindi distribuire la nuova applicazione).

Importante. Ricorda che la colonna appena creata NON DOVREBBE быть NON NULLA. Se esegui un rollback, la vecchia applicazione non riconosce la nuova colonna e non la installerà durante Insert. Ma se aggiungi questo vincolo e il tuo db lo sarà v2, ciò richiederà l'impostazione del valore della nuova colonna. Ciò porterà a violazioni delle restrizioni.

Importante. Dovresti rimuovere il metodo getLastName(), perché nella versione 3.0.0 Non esiste il concetto di colonna nel codice last_name. Ciò significa che lì verrà impostato null. Puoi lasciare il metodo e aggiungere controlli per null, ma una soluzione molto migliore sarebbe assicurarsi che ciò sia logico getSurname() hai selezionato il valore corretto diverso da zero.

Test A/B

La situazione attuale è che abbiamo una versione dell'applicazione 1.0.0, distribuito in produzione e il database in v1. Dobbiamo distribuire una seconda istanza della versione dell'applicazione 2.0.0che aggiornerà il database v2.

passaggi:

  1. viene distribuita una nuova istanza dell'applicazione versione 2.0.0che aggiorna il database v2
  2. nel frattempo alcune richieste sono state elaborate dalle istanze di versione 1.0.0
  3. l'aggiornamento ha avuto esito positivo e sono presenti più istanze in esecuzione della versione dell'applicazione 1.0.0 e altre versioni 2.0.0. Tutti comunicano con il database in v2
  4. versione 1.0.0 non utilizza la colonna del cognome nel database, ma la versione 2.0.0 usi. Non interferiscono tra loro e non dovrebbero esserci errori.
  5. versione 2.0.0 memorizza i dati sia nella vecchia che nella nuova colonna, garantendo la compatibilità con le versioni precedenti

Importante. Se hai query che contano gli elementi in base ai valori della colonna vecchia/nuova, dovresti ricordare che ora hai valori duplicati (molto probabilmente stanno ancora migrando). Ad esempio, se desideri contare il numero di utenti il ​​cui cognome (indipendentemente dal nome della colonna) inizia con la lettera A, quindi fino al completamento della migrazione dei dati (oldnew colonna) potresti avere dati incoerenti se esegui una query su una nuova colonna.

Rollback dell'applicazione

Ora abbiamo la versione dell'app 2.0.0 e database in v2.

passaggi:

  1. ripristina la versione dell'applicazione 1.0.0.
  2. versione 1.0.0 non utilizza una colonna nel database surname, quindi il rollback dovrebbe avere successo

Modifiche al DB

Il database contiene una colonna denominata last_name.

Script sorgente Flyway:

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');

Aggiungi sceneggiatura surname.

Attenzione. Ricorda che NON PUOI AGGIUNGERE alcun vincolo NOT NULL alla colonna che stai aggiungendo. Se esegui il rollback del JAR, la vecchia versione non avrà idea della colonna aggiunta e la imposterà automaticamente su NULL. Se esiste una tale limitazione, la vecchia applicazione semplicemente si interromperà.

-- NOTE: This field can't have the NOT NULL constraint cause if you rollback, the old version won't know about this field
-- and will always set it to NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Modifiche al codice

Archiviamo i dati come last_namee in surname. Allo stesso tempo leggiamo da last_name, poiché questa colonna è la più pertinente. Durante il processo di distribuzione, alcune richieste potrebbero essere state elaborate da un'istanza dell'applicazione non 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 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
                + "]";
    }
}

Passaggio 3: rimozione del cognome dal codice

Versione dell'applicazione: 3.0.0

Versione DB:v3

commento

Nota per.: A quanto pare, nell'articolo originale l'autore ha erroneamente copiato il testo di questo blocco dal passaggio 2. In questo passaggio dovrebbero essere apportate modifiche al codice dell'applicazione volte a rimuovere la funzionalità che utilizza la colonna last_name.

Aggiungendo una nuova colonna e copiandone il contenuto, abbiamo creato modifiche al database compatibili con le versioni precedenti. Inoltre, se eseguiamo il rollback del JAR o ne abbiamo uno vecchio in esecuzione, non si romperà durante l'esecuzione.

Rollback dell'applicazione

Attualmente abbiamo la versione dell'app 3.0.0 e banca dati v3. Versione 3.0.0 non salva i dati in last_name. Ciò significa che dentro surname vengono memorizzate le informazioni più aggiornate.

passaggi:

  1. ripristina la versione dell'applicazione 2.0.0.
  2. versione 2.0.0 usi e last_name и surname.
  3. versione 2.0.0 prenderà surname, se non è zero, altrimenti -last_name

Modifiche alla banca dati

Non ci sono cambiamenti strutturali nel database. Il seguente script viene eseguito per eseguire la migrazione finale dei vecchi dati:

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
-- ALSO WE'RE NOT CHECKING IF WE'RE NOT OVERRIDING EXISTING ENTRIES. WE WOULD HAVE TO COMPARE
-- ENTRY VERSIONS TO ENSURE THAT IF THERE IS ALREADY AN ENTRY WITH A HIGHER VERSION NUMBER
-- WE WILL NOT OVERRIDE IT.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- DROPPING THE NOT NULL CONSTRAINT; OTHERWISE YOU WILL TRY TO INSERT NULL VALUE OF THE LAST_NAME
-- WITH A NOT_NULL CONSTRAINT.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Modifiche al codice

Nota per.: Anche la descrizione di questo blocco è stata erroneamente copiata dall'autore dal passaggio 2. Secondo la logica dell'articolo, le modifiche al codice in questo passaggio dovrebbero mirare a rimuovere da esso elementi che funzionano con la colonna last_name.

Archiviamo i dati come last_namee in surname. Inoltre, leggiamo dalla colonna last_name, poiché è il più rilevante. Durante il processo di distribuzione, alcune richieste potrebbero 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
                + "]";
    }
}

Passaggio 4: rimozione del cognome dal database

Versione dell'applicazione: 4.0.0

Versione DB: v4

commento

A causa del fatto che il codice della versione 3.0.0 non ho usato la colonna last_name, non accadrà nulla di male durante l'esecuzione se torniamo a 3.0.0 dopo aver rimosso una colonna dal database.

Registri di esecuzione degli script

We will do it in the following way:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0
05) Wait for the app (2.0.0) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0
07) Generate a person by calling POST localhost:9992/person to version 2.0.0
08) Kill app (1.0.0)
09) Run 3.0.0
10) Wait for the app (3.0.0) to boot
11) Generate a person by calling POST localhost:9992/person to version 2.0.0
12) Generate a person by calling POST localhost:9993/person to version 3.0.0
13) Kill app (3.0.0)
14) Run 4.0.0
15) Wait for the app (4.0.0) to boot
16) Generate a person by calling POST localhost:9993/person to version 3.0.0
17) Generate a person by calling POST localhost:9994/person to version 4.0.0

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}

Starting app in version 2.0.0

Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:

{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}

Killing app 1.0.0

Starting app in version 3.0.0

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Killing app 2.0.0

Starting app in version 4.0.0

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:

{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}

Generate a person in version 4.0.0
Sending a post to 127.0.0.1:9994/person. This is the response:

{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}

Modifiche al DB

su v3 rimuoviamo semplicemente la colonna last_name e aggiungere le restrizioni mancanti.

-- REMOVE THE COLUMN
ALTER TABLE PERSON DROP last_name;

-- ADD CONSTRAINTS
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.

conclusione

Abbiamo implementato con successo una modifica del nome di colonna incompatibile con le versioni precedenti eseguendo diverse distribuzioni compatibili con le versioni precedenti. Di seguito un riepilogo delle azioni eseguite:

  1. distribuzione della versione dell'applicazione 1.0.0 с v1 schema del database (nome colonna = last_name)
  2. distribuzione della versione dell'applicazione 2.0.0, in cui vengono archiviati i dati last_name и surname. L'applicazione legge da last_name. Il database è in versione v2contenente colonne come last_nameE surname. surname è una copia della last_name. (NOTA: questa colonna non deve avere un vincolo non nullo)
  3. distribuzione della versione dell'applicazione 3.0.0, che memorizza solo i dati in surname e legge dal cognome. Per quanto riguarda il database, è in corso l'ultima migrazione last_name в surname. Anche una limitazione NON NULLA rimosso da last_name. Il database è ora in versione v3
  4. distribuzione della versione dell'applicazione 4.0.0 - non vengono apportate modifiche al codice. Distribuzione del database v4, che rimuove last_name. Qui puoi aggiungere eventuali vincoli mancanti al database.

Seguendo questo approccio è sempre possibile eseguire il rollback di una versione senza interrompere la compatibilità del database/applicazione.

codice

Tutto il codice utilizzato in questo articolo è disponibile all'indirizzo Github. Di seguito è riportata una descrizione aggiuntiva.

Progetti

Dopo aver clonato il repository, vedrai la seguente struttura di cartelle.

├── boot-flyway-v1              - 1.0.0 version of the app with v1 of the schema
├── boot-flyway-v2              - 2.0.0 version of the app with v2 of the schema (backward-compatible - app can be rolled back)
├── boot-flyway-v2-bad          - 2.0.0.BAD version of the app with v2bad of the schema (backward-incompatible - app cannot be rolled back)
├── boot-flyway-v3              - 3.0.0 version of the app with v3 of the schema (app can be rolled back)
└── boot-flyway-v4              - 4.0.0 version of the app with v4 of the schema (app can be rolled back)

Script

È possibile eseguire gli script descritti negli script seguenti, che dimostreranno le modifiche retrocompatibili e incompatibili al database.

Vedere il caso con modifiche compatibili con le versioni precedenti, correre:

./scripts/scenario_backward_compatible.sh

E vedere caso con modifiche retrocompatibili, correre:

./scripts/scenario_backward_incompatible.sh

Flyway campione di avvio primaverile

Tutti gli esempi sono tratti da Spring Boot Sample Flyway.

Puoi dare un'occhiata http://localhost:8080/flyway, c'è un elenco di script.

Questo esempio include anche la console H2 (at http://localhost:8080/h2-console) in modo da poter visualizzare lo stato del database (l'URL jdbc predefinito è jdbc:h2:mem:testdb).

Inoltre

Leggi anche altri articoli sul nostro blog:

Fonte: habr.com

Aggiungi un commento