Zero Downtime Deployment och databaser

Zero Downtime Deployment och databaser

Denna artikel förklarar i detalj hur man hanterar problem relaterade till databas-kompatibilitet vid deployment. Vi kommer att berätta vad som kan hända med dina produktionsapplikationer om du försöker göra en deployment utan förberedelser. Därefter kommer vi att gå igenom stegen i applikationens livscykel som krävs för att uppnå noll driftstopp (anm. övers.: nedan – zero downtime). Resultatet av våra operationer kommer att vara att tillämpa en bakåtstridande databasändring på ett framåtriktat kompatibelt sätt.

Om du vill förstå kodexemplen i artikeln, kan du hittar dem på GitHub.

Introduktion

Zero downtime deployment

Vad är detta mystiska zero downtime deployment? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.

Hur når man detta? Det finns flera sätt, här är ett av dem:

  • distribuera version nr 1 av din tjänst
  • genomföra databas-migration
  • distribuera version nr 2 av din tjänst parallellt med version nr 1
  • så snart du ser att version nr 2 fungerar som den ska, ta bort version nr 1
  • klart!

Lätt, eller hur? Tyvärr är det inte så enkelt, och vi kommer att gå igenom detta mer ingående senare. Nu, låt oss kolla på en annan ganska vanlig deployment-process – blue green deployment.

Har du någonsin hört talas om blue green deployment? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на denna artikel, där vi beskriver detta mer detaljerat. För att kort sammanfatta, påminner vi om hur man gör blue green deployment:

  • se till att ha två kopior av din produktionskod (“blue” och “green”);
  • dirigera all trafik till blue-miljön, dvs. så att produktions-URL:erna pekar dit;
  • distribuera och testa alla applikationsändringar i green-miljön;
  • byta URL:erna från blue till green-miljön

Blue green deployment är en metod som gör att du enkelt kan införa nya funktioner utan att oroa dig för att produktionen ska gå sönder. Detta beror på att även om något skulle hända, kan du enkelt rulla tillbaka till föregående miljö, helt enkelt genom att "vända på en switch".

Efter att ha läst allt ovan kan du ställa dig frågan: Vilken relation har zero downtime till Blue green deployment?

Tja, de har ganska mycket gemensamt, eftersom underhåll av två kopior av samma miljö kräver dubbla ansträngningar. Det är därför vissa team, som Martin Fowler hävdar, föredrar en variation av denna metod: Martin Fowler:

ett annat alternativ är att använda samma databas, vilket skapar blågröna svitchar för webb- och domänlager. I en sådan metod kan databaser ofta bli ett problem, särskilt när du behöver ändra dess schema för att stödja en ny version av programvaran.

Och här når vi huvudproblemet i den här artikeln. Databas. Låt oss titta på denna fras igen.

genomför databasmigration.

Nu måste du ställa dig frågan - vad händer om ändringen av databasen blir bakåtkompatibel? Kommer inte min första version av applikationen att gå sönder? Faktum är att det är just vad som kommer att hända…

Så trots de stora fördelarna med zero downtime / blue green deployment, tenderar företag att följa den nästa mer säkra processen för att distribuera sina applikationer:

  • förbered ett paket med den nya versionen av applikationen
  • stäng av den körande applikationen
  • köra skript för att migrera databasen
  • distribuera och starta den nya versionen av applikationen

I den här artikeln kommer vi att beskriva i detalj hur du kan arbeta med databasen och koden för att dra nytta av fördelarna med zero downtime deployment.

Databasproblem

Om du har en stateless applikation som inte lagrar några data i databasen kan du uppnå zero downtime deployment direkt. Tyvärr behöver större delen av programvaran på något sätt lagra data. Det är därför du måste tänka efter två gånger innan du gör några ändringar i schemat. Innan vi dyker in i detaljerna om hur man ändrar schemat på ett sätt som möjliggör distribution utan driftstopp, låt oss först fokusera på versionshanteringsschemat.

Versionshanteringsschema

I den här artikeln kommer vi att använda Flyway som ett verktyg för versionshantering (övers. anm.: vi talar om databas-migrationer). Självklart kommer vi också att skriva en Spring Boot-applikation som har inbyggd stöd för Flyway och kommer att utföra schemamigrering vid inställning av applikationskontext. Genom att använda Flyway kan du lagra migreringsskript i mappen för dina projekt (som standard i classpath:db/migration). Här kan du se ett exempel på sådana migreringsfiler

└── db
 └── migration
 ├── V1__init.sql
 ├── V2__Add_surname.sql
 ├── V3__Final_migration.sql
 └── V4__Remove_lastname.sql

I det här exemplet ser vi fyra migrationsscenarier som, om de inte har utförts tidigare, kommer att utföras en efter en vid start av applikationen. Låt oss titta på en av filerna (V1__init.sql) som ett exempel.

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

Allt talar för sig självt: du kan använda SQL för att ange hur din databas ska ändras. För mer information om Spring Boot och Flyway, kolla in Spring Boot Docs.

Genom att använda ett versionshanteringsverktyg med Spring Boot får du två stora fördelar:

  • du separerar databasändringar från kodändringar
  • databasens migration sker tillsammans med distributionen av din applikation, dvs. din distributionsprocess förenklas

Problem med databasen

I nästa avsnitt av artikeln kommer vi att fokusera på att titta på två tillvägagångssätt för databasändringar.

  • bakåtkompatibilitet
  • bakåtkompatibilitet

Den första kommer att behandlas som en varning att man inte ska göra zero downtime-distribution utan föregående förberedelse… Den andra erbjuder en lösning på hur man kan göra distribution utan driftstopp och samtidigt bibehålla bakåtkompatibilitet.

Vårt projekt som vi kommer att arbeta med kommer att vara en enkel Spring Boot Flyway-applikation som har Person med first_name och last_name i databasen (anm. övers.: Person är en tabell, ochfirst_name och last_name — är fälten i den). Vi vill döpa om till last_name på surname.

Antaganden

Innan vi går in på detaljerna är det viktigt att klargöra några antaganden angående våra applikationer. Det huvudsakliga resultat vi vill uppnå kommer att vara en ganska enkel process.

Notera. Business PRO-TIP. Att förenkla processer kan spara dig mycket pengar på support (ju fler människor som arbetar i ditt företag, desto mer pengar kan du spara)!

Det ska inte göras någon databasåterställning

Detta förenklar distributionsprocessen (vissa databasåterställningar är praktiskt taget omöjliga, som att återställa en radering). Vi föredrar att endast återställa applikationer. På så sätt ser din distributionspipeline likadan ut även om du har olika databaser (t.ex. SQL och NoSQL).

Det måste alltid finnas möjlighet att återställa applikationen till en version tillbaka (inte mer)

Rullback bör endast göras vid behov. Om det finns ett fel i den aktuella versionen som är svårt att åtgärda, bör vi kunna återgå till den senaste fungerande versionen. Vi antar att denna senaste fungerande version är den föregående. Att stödja kompatibiliteten för kod och databas för mer än en distribution skulle vara extremt svårt och kostsamt.

Anteckning. För att öka läsbarheten, kommer vi i den här artikeln att ändra majorversionen av applikationen.

Steg 1: Ursprungligt tillstånd

Applikationens version: 1.0.0
Databasens version: v1

Kommentar

Detta kommer att vara det ursprungliga tillståndet för applikationen.

Ändringar i databasen

Databasen innehåller 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');

Ändringar i koden

Applikationen sparar Person-data i 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
                + "]";
    }
}

Bakåtkompatibel omdöpning av kolumn

Låt oss se ett exempel på hur man ändrar kolumnnamnet:

Observera. Följande exempel kommer avsiktligt att orsaka fel. Vi visar detta för att demonstrera problematiken med databasens kompatibilitet.

Applikationens version: 2.0.0.BAD

Databasens version: v2bad

Kommentar

Nuvarande ändringar TILLÅTER oss INTE att köra två instanser (gammal och ny) samtidigt. Därför blir zero downtime deployment svårt att uppnå (med vissa antaganden är det faktiskt omöjligt).

A/B-testning

Den nuvarande situationen är att vi har en applikation av versionen 1.0.0, implementerad i produktion, och databasen v1. Vi måste distribuera en andra instans av applikationen, version 2.0.0.BAD, och uppdatera databasen till v2bad.

Steg:

  1. har distribuerats en ny instans av applikationen version 2.0.0.BAD, som uppdaterar databasen till v2bad
  2. i databasen v2bad kolumnen last_name finns inte längre — den har ändrats till surname
  3. uppdateringen av databasen och applikationen har genomförts framgångsrikt, och vissa instanser körs i 1.0.0, andra i 2.0.0.BAD. Alla är kopplade till databasen v2bad
  4. alla instanser av versionen 1.0.0 kommer att börja ge fel, eftersom de kommer att försöka infoga data i kolumnen last_name, som inte längre finns
  5. alla instanser av versionen 2.0.0.BAD kommer att fungera utan problem

Som du ser, om vi gör bakåtkompatibla ändringar i databasen och applikationen, är A/B-testning omöjligt.

Återställning av applikationen

Låt oss anta att efter att ha försökt genomföra en A/B-distribution (not: förmodligen avser författaren A/B-testning) beslutade vi att vi behövde återgå applikationen till versionen 1.0.0. Antag att vi inte vill göra en återställning av databasen.

Steg:

  1. vi stoppar applikationsinstansen i version 2.0.0.BAD
  2. databasen är fortfarande v2bad
  3. eftersom versionen 1.0.0 inte förstår vad som menas med surname, vi kommer att se fel
  4. helvetet har släppts löst, vi kan inte gå tillbaka längre

Som ni ser, om vi gör bakåtkompatibla förändringar av databasen och applikationen, kan vi inte rulla tillbaka till den föregående versionen.

Loggar för skriptkörning

Bakåtkompatibelt scenario:

01) Kör 1.0.0
02) Vänta på att appen (1.0.0) ska starta
03) Generera en person genom att anropa POST localhost:9991/person till version 1.0.0
04) Kör 2.0.0.BAD
05) Vänta på att appen (2.0.0.BAD) ska starta
06) Generera en person genom att anropa POST localhost:9991/person till version 1.0.0 <-- detta bör misslyckas
07) Generera en person genom att anropa POST localhost:9992/person till version 2.0.0.BAD <-- detta bör lyckas

Startar appen i version 1.0.0
Generera en person i version 1.0.0
Skickar ett POST-anrop till 127.0.0.1:9991/person. Detta är svaret:

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

Startar appen i version 2.0.0.BAD
Generera en person i version 1.0.0
Skickar ett POST-anrop till 127.0.0.1:9991/person. Detta är svaret:

curl: (22) Begärd URL returnerade fel: 500 Internt serverfel

Generera en person i version 2.0.0.BAD
Skickar ett POST-anrop till 127.0.0.1:9995/person. Detta är svaret:

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

Ändringar i databasen

Migrationsskriptet som döper om last_name på surname

Ursprungligt Flyway-skript:

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

Skriptet som döper om last_name.

-- Denna ändring är bakåtkompatibel - du kan inte göra A/B-testning
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Ändringar i koden

Vi har ändrat namn på fältet lastName till surname.

Döp om kolumnen på ett bakåtkompatibelt sätt

Detta är den vanligaste situationen som vi kan stöta på. Vi måste göra bakåtinkompatibla förändringar. Vi har redan bevisat att för att göra en driftsättning utan driftstopp måste vi inte bara tillämpa databasmigreringarna utan ytterligare åtgärder. I detta avsnitt av artikeln kommer vi att utföra 3 driftsättningar av applikationen tillsammans med databasmigreringar för att nå önskat resultat och samtidigt behålla bakåtkompatibiliteten.

Notera. Kom ihåg att vi har en databasversion v1. Den innehåller kolumnerna first_name och last_name. Vi måste ändra last_name till surname. Vi har också en applikation version 1.0.0, som fortfarande inte använder surname.

Steg 2: Lägga till surname

Applikationens version: 2.0.0
Databasens version: v2

Kommentar

Genom att lägga till en ny kolumn och kopiera innehållet skapar vi bakåtkompatibla databasanpassningar. Samtidigt, om vi rullar tillbaka JAR:en eller om vi har en fungerande gammal JAR, bryts den inte under körning.

Vi rullar ut en ny version

Steg:

  1. genomför databasmigrering för att skapa den nya kolumnen surname. Nu är din databasversion v2
  2. kopiera data från last_name på surname. Observera, vilket, om du har mycket av dessa data, innebär att du bör överväga batchmigrering!
  3. skriv koden där de används BÅDA och avsnitt, och gammal kolumn. Nu är din appversion 2.0.0
  4. läs värdet från kolumnen surname, om det inte null, eller från last_name, om surname inte angivet. Du kan ta bort getLastName() från koden, eftersom det kommer att ge null vid återställning av din app med 3.0.0 till 2.0.0.

Om du använder Spring Boot Flyway kommer dessa två steg att utföras vid start av versionen 2.0.0 av applikationen. Om du kör databashanteringsverktyget manuellt måste du göra två olika åtgärder (först uppdatera db-versionen manuellt och sedan distribuera den nya applikationen).

Viktigt. Kom ihåg att den nyligen skapade kolumnen FÅR INTE vara NOT NULL. Om du gör en återställning vet inte den gamla applikationen om den nya kolumnen och kommer inte att ställa in den under Insert. Men om du lägger till detta begränsning och din databas är v2, kommer det att kräva att man sätter ett värde för den nya kolumnen. Detta kommer att leda till begränsningsbrott.

Viktigt. Du bör ta bort metoden getLastName(), eftersom det i versionen 3.0.0 inte finns något begrepp om kolumnen last_name. Det betyder att det kommer att ställas in på null. Du kan behålla metoden och lägga till kontroller för null, men det mycket bättre lösningen är att se till att logiken getSurname() är korrekt med ett icke-null värde.

A/B-testning

Den nuvarande situationen är att vi har en applikation av versionen 1.0.0, distribuerad i produktionen, och databasen i v1. Vi måste distribuera en andra instans av appversionen 2.0.0, som kommer att uppdatera databasen till v2.

Steg:

  1. har distribuerats en ny instans av applikationen version 2.0.0, som uppdaterar databasen till v2
  2. under tiden har vissa förfrågningar behandlats av versionens instanser 1.0.0
  3. uppdateringen genomfördes framgångsrikt, och du har flera fungerande instanser av appversionen 1.0.0 och andra versioner 2.0.0. Alla kommunicerar med databasen i v2
  4. version 1.0.0 använder inte kolumnen surname i databasen, medan versionen 2.0.0 anser. De stör inte varandra, och inga fel bör uppstå.
  5. version 2.0.0 sparar data både i den gamla och den nya kolumnen, vilket säkerställer bakåtkompatibilitet.

Viktigt. Om du har några förfrågningar som räknar element baserat på värden från den gamla / nya kolumnen, bör du komma ihåg att du nu har värden som dubblar (de migrerar förmodligen fortfarande). Till exempel, om du vill räkna antalet användare vars efternamn (vad kolumnen nu än kallas) börjar med bokstaven A, så fram till att datamigreringen är klar (old → new kolumn) kan du få inkonsekventa data om du gör en förfrågan till den nya kolumnen.

Återställning av applikationen

Nu har vi appversionen 2.0.0 och databasen i v2.

Steg:

  1. återställ din applikation till version 1.0.0.
  2. version 1.0.0 använder inte kolumnen i databasen surname, så återställningen bör lyckas

Ändringar i DB

DB:n innehåller en kolumn med namnet last_name.

Ursprungligt Flyway-skript:

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

Tilläggsskript surname.

Observera. Kom ihåg att DU INTE KAN LÄGGA TILL några NOT NULL-restriktioner på den tillagda kolumnen. Om du återställer JAR-filen, kommer den gamla versionen inte att känna till den tillagda kolumnen och kommer automatiskt att sätta den till NULL. Om det finns en sådan restriktion kommer den gamla applikationen helt enkelt att gå sönder.

-- OBS: Denna kolumn kan inte ha NOT NULL-restriktion eftersom om du återställer, kommer den gamla versionen inte att veta om denna kolumn
-- och kommer alltid att sätta den till NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- VI FÖRUTSÄTTER ATT DET ÄR EN SNABB MIGRATION - ANNARS SKULLE VI MÅSTE MIGRERA I OMgångar
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Ändringar i koden

Vi sparar data både i last_name.ts surname. Samtidigt läser vi från last_name, eftersom denna kolumn är mest relevant. Under deployment-processen kan vissa förfrågningar ha hanterats av en instans av applikationen som ännu inte hade uppdaterats.

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

Steg 3: Ta bort last_name från koden

Applikationens version: 3.0.0

Databasens version:v3

Kommentar

Anm. övers.: Uppenbarligen har texten i denna block kopierats felaktigt av författaren från steg 2 i den ursprungliga artikeln. I detta steg skall ändringar i applikationskoden göras för att ta bort funktionaliteten som använder kolumnen last_name.

Genom att lägga till en ny kolumn och kopiera dess innehåll skapade vi bakåtkompatibla ändringar i DB. Om vi återställer JAR-filen eller har en fungerande gammal JAR, kommer den inte att gå sönder under körning.

Återställning av applikationen

För närvarande har vi applikationen version 3.0.0 och databasen v3. Version 3.0.0 sparar inte data i last_name. Detta innebär att den mest aktuella informationen lagras i surname .

Steg:

  1. återställ din applikation till version 2.0.0.
  2. version 2.0.0 använder och last_name och surname.
  3. version 2.0.0 kommer att ta surname, om den inte är null, annars —last_name

Ändringar i databasen

Det finns inga strukturella förändringar i DB. Följande skript körs för att genomföra den slutliga migreringen av de gamla data:

-- VI FÖRUTSÄTTER ATT DET ÄR EN SNABB MIGRATION - ANNARS SKULLE VI MÅSTE MIGRERA I OMgångar
-- OCKSÅ KONTROLLERAR VI INTE OM VI INTE ÖVERTRÄDER EXISTERANDE ANSUBER. VI SKULLE BEHÖVA JÄMFÖRA
-- ANMÄLNINGAR FÖR ATT SÄKERSTÄLLA ATT OM DET REDAN FINNS EN ANMÄLAN MED HÖGRE VERSIONNUMMER
-- KOMMER VI INTE ATT ÖVERTRÄDA DEN.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- TA BORT NOT NULL-RESTRIKTIONEN; ANNARS FÖRSÖKER DU ATT INFÖRA NULL-VÄRDE PÅ LAST_NAME
-- MED NOT_NULL RESTRIKTION.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Ändringar i koden

Anm. övers.: Beskrivningen av detta block har även felaktigt kopierats av författaren från steg 2. I enlighet med berättelsens logik i artikeln bör ändringarna i koden i detta steg syfta till att ta bort de element som hanterar kolumnen last_name.

Vi lagrar data både i last_name.ts surname. Dessutom läser vi från kolumnen last_name, eftersom den är mest relevant. Under distributionen kan vissa förfrågningar behandlas av en instans som ännu inte har uppdaterats.

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

Steg 4: Ta bort last_name från databasen

Applikationens version: 4.0.0

Databasens version: v4

Kommentar

På grund av att versionskoden 3.0.0 inte använde kolumnen last_name, under körningen kommer inget dåligt att hända om vi återgår till 3.0.0 efter att kolumnen har tagits bort från databasen.

Loggar för skriptkörning

Vi kommer att göra det på följande sätt:

01) Starta 1.0.0
02) Vänta på att appen (1.0.0) startar
03) Generera en person genom att anropa POST localhost:9991/person till version 1.0.0
04) Starta 2.0.0
05) Vänta på att appen (2.0.0) startar
06) Generera en person genom att anropa POST localhost:9991/person till version 1.0.0
07) Generera en person genom att anropa POST localhost:9992/person till version 2.0.0
08) Stäng appen (1.0.0)
09) Starta 3.0.0
10) Vänta på att appen (3.0.0) startar
11) Generera en person genom att anropa POST localhost:9992/person till version 2.0.0
12) Generera en person genom att anropa POST localhost:9993/person till version 3.0.0
13) Stäng appen (3.0.0)
14) Starta 4.0.0
15) Vänta på att appen (4.0.0) startar
16) Generera en person genom att anropa POST localhost:9993/person till version 3.0.0
17) Generera en person genom att anropa POST localhost:9994/person till version 4.0.0

Startar appen i version 1.0.0
Generera en person i version 1.0.0
Skickar en post till 127.0.0.1:9991/person. Detta är svaret:

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

Startar appen i version 2.0.0

Generera en person i version 1.0.0
Skickar en post till 127.0.0.1:9991/person. Detta är svaret:

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

Generera en person i version 2.0.0
Skickar en post till 127.0.0.1:9992/person. Detta är svaret:

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

Stänger appen 1.0.0

Startar appen i version 3.0.0

Generera en person i version 2.0.0
Skickar en post till 127.0.0.1:9992/person. Detta är svaret:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Generera en person i version 3.0.0
Skickar en post till 127.0.0.1:9993/person. Detta är svaret:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Stänger appen 2.0.0

Startar appen i version 4.0.0

Generera en person i version 3.0.0
Skickar en post till 127.0.0.1:9993/person. Detta är svaret:

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

Generera en person i version 4.0.0
Skickar en post till 127.0.0.1:9994/person. Detta är svaret:

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

Ändringar av databasen

Angående v3 tar vi helt enkelt bort kolumnen last_name och lägger till de saknade begränsningarna.

-- TA BORT KOLUMNEN
ALTER TABLE PERSON DROP last_name;

-- LÄGG TILL BEGRÄNSNINGAR
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Ändringar i koden

Ändringar i koden saknas.

Utdata

Vi har framgångsrikt tillämpat bakåtkompatibla ändringar av kolumnnamnet genom att genomföra flera bakåtkompatibla distributioner. Nedan följer en sammanfattning av de genomförda åtgärderna:

  1. distribution av applikationen version 1.0.0 med v1 databas schema (kolumnnamn = last_name)
  2. distribution av applikationen version 2.0.0, som sparar data i last_name och surname. Applikationen läser från last_name. Databasen är i version v2, som innehåller kolumner som last_name, såväl som surname. surname är en kopia av last_name. (OBS: denna kolumn får inte ha begränsningen not null)
  3. distribution av applikationen version 3.0.0, som sparar data endast i surname och läser från surname. När det gäller databasen sker den senaste migreringen last_name på surname. Även begränsningen NOT NULL tas bort från last_name. Databasen är nu i version v3
  4. distribution av applikationen version 4.0.0 — inga ändringar görs i koden. Databasen deployas v4, som tar bort last_name. Här kan du lägga till eventuella saknade begränsningar i databasen.

Genom att följa detta tillvägagångssätt kan du alltid återgå till en tidigare version utan att bryta kompatibiliteten mellan databasen / applikationen.

Kod

All kod som används i den här artikeln är tillgänglig på Github. Nedan finns en ytterligare beskrivning.

Projekt

Efter kloning av repositoriet kommer du att se följande mappstruktur.

├── boot-flyway-v1              - 1.0.0 version av appen med v1 av schemat
├── boot-flyway-v2              - 2.0.0 version av appen med v2 av schemat (bakåtkompatibel - app kan rullas tillbaka)
├── boot-flyway-v2-bad          - 2.0.0.BAD version av appen med v2bad av schemat (bakåtinkompatibel - app kan inte rullas tillbaka)
├── boot-flyway-v3              - 3.0.0 version av appen med v3 av schemat (app kan rullas tillbaka)
└── boot-flyway-v4              - 4.0.0 version av appen med v4 av schemat (app kan rullas tillbaka)

Skript

Du kan köra skripten som beskrivs nedan, vilka demonstrerar bakåtkompatibla och inkompatibla förändringar i databasen.

För att se fallet med bakåtkompatibla förändringar, kör:

.\/scripts\/scenario_backward_compatible.sh

Och för att se fallet med bakåtinkompatibla förändringar, kör:

.\/scripts\/scenario_backward_incompatible.sh

Spring Boot Sample Flyway

Alla exempel är hämtade från Spring Boot Sample Flyway.

Du kan titta på http://localhost:8080/flyway, där det finns en lista över skript.

Det här exemplet inkluderar också H2-konsolen (vid http://localhost:8080/h2-console), så att du kan se status på databasen (standard JDBC URL — jdbc:h2:mem:testdb).

Ytterligare

Läs även andra artiklar i vår blogg:

Källa: habr.com

Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar 🔥 Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster