Null nedetidsdistribusjon og databaser

Null nedetidsdistribusjon og databaser

Denne artikkelen forklarer i detalj hvordan du løser problemer med databasekompatibilitet under distribusjon. Vi vil fortelle deg hva som kan skje med produksjonsapplikasjonene dine hvis du prøver å distribuere uten foreløpig forberedelse. Vi vil deretter gå gjennom applikasjonens livssyklusstadier som kreves for å ha null nedetid (ca. kjørefelt: videre - null nedetid). Resultatet av våre operasjoner vil være å bruke den bakover-inkompatible databaseendringen på en bakoverkompatibel måte.

Hvis du vil forstå kodeeksemplene fra artikkelen, finner du dem på GitHub.

Innledning

Null nedetid distribusjon

For en mystisk null nedetid distribusjon? Du kan si at dette er når applikasjonen din er distribuert på en slik måte at du kan introdusere en ny versjon av applikasjonen til produksjon, mens brukeren ikke merker at den ikke er tilgjengelig. Fra et bruker- og bedriftsperspektiv er dette det best mulige distribusjonsscenarioet fordi det lar nye funksjoner introduseres og feil fikses uten avbrudd.

Hvordan oppnå dette? Det er flere måter, her er en av dem:

  • distribuer versjon nr. 1 av tjenesten din
  • utføre en databasemigrering
  • Distribuer versjon #2 av tjenesten din parallelt med versjon #1
  • så snart du ser at versjon nr. 2 fungerer som den skal, fjerner du versjon nr. 1
  • er klar!

Lett, ikke sant? Dessverre er det ikke så enkelt, og vi skal se nærmere på det senere. La oss nå sjekke en annen ganske vanlig distribusjonsprosess - blågrønn distribusjon.

Har du noen gang hørt om blågrønn utplassering? Cloud Foundry gjør dette ekstremt enkelt. Bare se på denne artikkelen, hvor vi beskriver dette nærmere. For å oppsummere kort, la oss minne deg på hvordan du implementerer blågrønn:

  • sørge for at to kopier av produksjonskoden din ("blå" og "grønn") fungerer;
  • lede all trafikk til det blå miljøet, d.v.s. slik at produksjons-URL-er peker dit;
  • distribuere og teste alle applikasjonsendringer i et grønt miljø;
  • bytt nettadresser fra blått til grønt miljø

Blågrønn distribusjon er en tilnærming som lar deg enkelt introdusere nye funksjoner uten å bekymre deg for produksjonsbrudd. Dette er på grunn av det faktum at selv om noe skjer, kan du enkelt rulle tilbake til det forrige miljøet ved å "svinge en bryter."

Etter å ha lest alt ovenfor, kan du stille spørsmålet: Hva har null nedetid å gjøre med blågrønn distribusjon?

Vel, de har ganske mye til felles, siden vedlikehold av to kopier av samme miljø krever dobbelt innsats for å vedlikeholde dem. Dette er grunnen til at noen lag hevder Martin Fowler, følg en variant av denne tilnærmingen:

Et annet alternativ er å bruke den samme databasen, og lage blågrønne brytere for web- og domenelag. I denne tilnærmingen kan databasen ofte være et problem, spesielt når du må endre skjemaet for å støtte en ny versjon av programvaren.

Og her kommer vi til hovedproblemet i denne artikkelen. Database. La oss ta en ny titt på denne setningen.

utføre en databasemigrering.

Nå må du stille deg selv spørsmålet - hva om databaseendringen ikke er bakoverkompatibel? Vil ikke min første versjon av appen gå i stykker? Faktisk er det akkurat dette som vil skje...

Så selv til tross for de enorme fordelene med null nedetid / blågrønn distribusjon, har selskaper en tendens til å følge følgende sikrere prosess for å distribuere applikasjonene sine:

  • klargjør en pakke med en ny versjon av applikasjonen
  • slå av et program som kjører
  • kjøre skript for å migrere databasen
  • distribuere og lansere en ny versjon av applikasjonen

I denne artikkelen beskriver vi hvordan du kan jobbe med databasen og koden din for å dra nytte av null nedetidsdistribusjon.

Databaseproblemer

Hvis du har et statsløst program som ikke lagrer noen data i databasen, kan du få null nedetidsdistribusjon med en gang. Dessverre må de fleste programvare lagre data et sted. Dette er grunnen til at du bør tenke deg om to ganger før du gjør endringer i kretsen. Før vi går inn på detaljene for hvordan du endrer skjemaet slik at distribusjon uten nedetid er mulig, la oss først fokusere på versjonsskjemaet.

Versjonsordning

I denne artikkelen skal vi bruke flyway som et versjonskontrollverktøy (ca. Oversettelse: vi snakker om databasemigreringer). Naturligvis vil vi også skrive en Spring Boot-applikasjon som har innebygd Flyway-støtte og vil utføre skjemamigrering mens du setter opp applikasjonskonteksten. Når du bruker Flyway, kan du lagre migreringsskript i prosjektmappen din (som standard i classpath:db/migration). Her kan du se et eksempel på slike migreringsfiler

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

I dette eksemplet ser vi 4 migreringsskript som, hvis de ikke er utført tidligere, vil bli kjørt etter hverandre når applikasjonen starter. La oss se på en av filene (V1__init.sql) som et eksempel.

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

Alt er perfekt selvforklarende: du kan bruke SQL til å definere hvordan databasen din skal endres. For mer informasjon om Spring Boot og Flyway, sjekk ut Spring Boot Docs.

Ved å bruke et kildekontrollverktøy med Spring Boot får du 2 store fordeler:

  • du skiller databaseendringer fra kodeendringer
  • Databasemigrering skjer sammen med utrullingen av applikasjonen din, dvs. distribusjonsprosessen din er forenklet

Feilsøking av databaseproblemer

I neste del av artikkelen vil vi fokusere på å se på to tilnærminger til databaseendringer.

  • bakoverkompatibilitet
  • bakoverkompatibilitet

Den første vil bli betraktet som en advarsel om at du ikke bør utføre null nedetid distribusjon uten foreløpig forberedelse... Den andre tilbyr en løsning på hvordan du kan utføre en distribusjon uten nedetid og samtidig opprettholde bakoverkompatibilitet.

Prosjektet vårt vi skal jobbe med vil være en enkel Spring Boot Flyway-applikasjon som har Person с first_name и last_name i databasen (ca. oversettelse: Person er et bord og first_name и last_name - dette er feltene i den). Vi ønsker å gi nytt navn last_name в surname.

Antagelser

Før vi går inn i detaljene, er det et par forutsetninger vi må gjøre om søknadene våre. Hovedresultatet vi ønsker å oppnå vil være en ganske enkel prosess.

Notatet. Business PRO-TIPS. Å forenkle prosessene dine kan spare deg for mye penger på support (jo flere personer du har som jobber for bedriften din, jo mer penger kan du spare)!

Ingen grunn til å rulle tilbake databasen

Dette forenkler distribusjonsprosessen (noen tilbakeføringer av databaser er nesten umulige, for eksempel tilbakeføring av sletting). Vi foretrekker å rulle tilbake kun applikasjoner. På denne måten, selv om du har forskjellige databaser (for eksempel SQL og NoSQL), vil distribusjonspipelinen din se lik ut.

Det må ALLTID være mulig å rulle tilbake applikasjonen én versjon tilbake (ikke mer)

Tilbakerulling bør kun gjøres når det er nødvendig. Hvis det er en feil i den nåværende versjonen som ikke er lett å fikse, bør vi kunne gå tilbake til den siste fungerende versjonen. Vi antar at denne siste fungerende versjonen er den forrige. Å opprettholde kode- og databasekompatibilitet for mer enn én utrulling ville være ekstremt vanskelig og dyrt.

Notatet. For større lesbarhet vil vi i denne artikkelen endre hovedversjonen av applikasjonen.

Trinn 1: Utgangstilstand

Appversjon: 1.0.0
DB versjon: v1

Kommentar

Dette vil være den opprinnelige tilstanden til søknaden.

Databaseendringer

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

Kodeendringer

Applikasjonen lagrer persondata 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
                + "]";
    }
}

Gi nytt navn til kolonner som er inkompatibel bakover

La oss se på et eksempel på hvordan du endrer et kolonnenavn:

Merk følgende. Følgende eksempel vil med vilje bryte ting. Vi viser dette for å demonstrere problemet med databasekompatibilitet.

Appversjon: 2.0.0.BAD

DB versjon: v2bad

Kommentar

De gjeldende endringene tillater IKKE at vi kjører to forekomster (gamle og nye) samtidig. Dermed vil null nedetidsdistribusjon være vanskelig å oppnå (hvis forutsetninger tas i betraktning, er det faktisk umulig).

A/B-testing

Den nåværende situasjonen er at vi har en applikasjonsversjon 1.0.0, distribuert i produksjon og database v1. Vi må distribuere en andre forekomst av applikasjonen, versjon 2.0.0.BAD, og oppdater databasen til v2bad.

Trinn:

  1. en ny forekomst av versjonsapplikasjonen er distribuert 2.0.0.BADsom oppdaterer databasen til v2bad
  2. i databasen v2bad kolonne last_name eksisterer ikke lenger - den ble endret til surname
  3. Databasen og applikasjonsoppdateringen var vellykket, og noen forekomster kjører 1.0.0, andre - i 2.0.0.BAD. Alt er koblet til databasen v2bad
  4. alle forekomster av versjonen 1.0.0 vil begynne å kaste feil fordi de vil prøve å sette inn data i kolonnen last_namesom ikke lenger eksisterer
  5. alle forekomster av versjonen 2.0.0.BAD vil fungere uten problemer

Som du kan se, hvis vi gjør bakover-inkompatible endringer i databasen og applikasjonen, er A/B-testing umulig.

Tilbakeføring av søknad

La oss anta at etter å ha prøvd å gjøre A/B-distribusjon (ca. pr.: forfatteren mente nok A/B-testing her) bestemte vi oss for at vi må rulle tilbake applikasjonen til versjonen 1.0.0. La oss si at vi ikke vil tilbakestille databasen.

Trinn:

  1. vi stopper versjonsapplikasjonsinstansen 2.0.0.BAD
  2. databasen er fortsatt v2bad
  3. siden versjonen 1.0.0 skjønner ikke hva det er surname, vil vi se feil
  4. helvete har brutt løs, vi kan ikke gå tilbake lenger

Som du kan se, hvis vi gjør bakover-inkompatible endringer i databasen og applikasjonen, kan vi ikke rulle tilbake til forrige versjon.

Logger for utførelse av skript

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"}

Databaseendringer

Migreringsskript som gir nytt navn last_name в surname

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

Skript som gir nytt navn last_name.

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

Kodeendringer

Vi har endret feltnavnet lastNamesurname.

Gi nytt navn til en kolonne på en bakoverkompatibel måte

Dette er den vanligste situasjonen vi kan møte. Vi må gjøre endringer som er uforenlige bakover. Vi har allerede bevist at for distribusjon med null nedetid, bør vi ikke bare bruke databasemigrering uten ytterligere trinn. I denne delen av artikkelen vil vi utføre 3 distribusjoner av applikasjonen sammen med databasemigreringer for å oppnå ønsket resultat og samtidig opprettholde bakoverkompatibilitet.

Notatet. Husk at vi har en versjonsdatabase v1. Den inneholder kolonner first_name и last_name. Vi må endre oss last_namesurname. Vi har også appversjon 1.0.0, som ennå ikke er brukt surname.

Trinn 2: Legg til etternavn

Appversjon: 2.0.0
DB versjon: v2

Kommentar

Ved å legge til en ny kolonne og kopiere innholdet, lager vi bakoverkompatible databaseendringer. Samtidig, hvis vi ruller tilbake JAR eller har en gammel JAR i gang, vil den ikke gå i stykker under utførelse.

Vi ruller ut en ny versjon

Trinn:

  1. utføre en databasemigrering for å opprette en ny kolonne surname. Nå din DB-versjon v2
  2. kopiere data fra last_name в surname. Vær oppmerksomat hvis du har mye av disse dataene, bør du vurdere batchmigrering!
  3. skriv koden der de brukes BÅDE и nyttOg den gamle kolonne. Nå er appversjonen din 2.0.0
  4. les verdien fra kolonnen surname, hvis det ikke er det null, eller fra last_name, hvis surname ikke spesifisert. Du kan slette getLastName() fra koden, siden den vil sendes ut null når du ruller tilbake søknaden fra 3.0.0 til 2.0.0.

Hvis du bruker Spring Boot Flyway, vil disse to trinnene bli utført under oppstart av versjonen 2.0.0 applikasjoner. Hvis du kjører databaseversjonsverktøyet manuelt, må du gjøre to forskjellige ting for å gjøre dette (først oppdatere db-versjonen manuelt og deretter distribuere den nye applikasjonen).

Viktig. Husk at den nyopprettede kolonnen BURDE IKKE være IKKE NULL. Hvis du gjør en tilbakestilling, vet ikke den gamle applikasjonen om den nye kolonnen og vil ikke installere den under Insert. Men hvis du legger til denne begrensningen, så blir db v2, vil dette kreve å angi verdien for den nye kolonnen. Noe som vil føre til brudd på restriksjoner.

Viktig. Du bør fjerne metoden getLastName(), fordi i versjonen 3.0.0 Det er ikke noe konsept for en kolonne i koden last_name. Dette betyr at null vil bli satt der. Du kan forlate metoden og legge til sjekker for null, men en mye bedre løsning ville være å sørge for at i logikken getSurname() du valgte riktig verdi som ikke er null.

A/B-testing

Den nåværende situasjonen er at vi har en applikasjonsversjon 1.0.0, distribuert på produksjon, og databasen i v1. Vi må distribuere en andre forekomst av versjonsapplikasjonen 2.0.0som vil oppdatere databasen til v2.

Trinn:

  1. en ny forekomst av versjonsapplikasjonen er distribuert 2.0.0som oppdaterer databasen til v2
  2. i mellomtiden ble noen forespørsler behandlet av versjonsforekomster 1.0.0
  3. oppdateringen var vellykket, og du har flere kjørende forekomster av versjonsapplikasjonen 1.0.0 og andre versjoner 2.0.0. Alle kommuniserer med databasen i v2
  4. versjon 1.0.0 bruker ikke etternavnskolonnen i databasen, men versjonen 2.0.0 bruker. De forstyrrer ikke hverandre, og det skal ikke være noen feil.
  5. versjon 2.0.0 lagrer data i både den gamle og nye kolonnen, og sikrer bakoverkompatibilitet

Viktig. Hvis du har noen spørsmål som teller elementer basert på verdier fra den gamle/nye kolonnen, bør du huske at du nå har dupliserte verdier (mest sannsynlig migrerer de fortsatt). For eksempel, hvis du vil telle antall brukere hvis etternavn (uansett hvilken kolonne heter) begynte med bokstaven A, deretter til datamigreringen er fullført (oldnew kolonne) kan du ha inkonsistente data hvis du spør etter en ny kolonne.

Tilbakeføring av søknad

Nå har vi appversjon 2.0.0 og database i v2.

Trinn:

  1. rulle tilbake applikasjonen til versjon 1.0.0.
  2. versjon 1.0.0 bruker ikke en kolonne i databasen surname, så tilbakeføringen skal være vellykket

DB endringer

Databasen inneholder en kolonne med navn last_name.

Flyway-kildeskript:

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

Legg til skript surname.

Merk følgende. Husk at du IKKE KAN LEGG TIL NOEN NOT NULL-begrensninger i kolonnen du legger til. Hvis du ruller tilbake JAR, vil den gamle versjonen ikke ha noen anelse om kolonnen som ble lagt til, og vil automatisk sette den til NULL. Hvis det er en slik begrensning, vil den gamle applikasjonen ganske enkelt gå i stykker.

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

Kodeendringer

Vi lagrer data som last_name, og i surname. Samtidig leser vi fra last_name, siden denne kolonnen er den mest relevante. Under distribusjonsprosessen kan noen forespørsler ha blitt behandlet av en applikasjonsforekomst som ennå ikke er oppdatert.

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

Trinn 3: Fjerner etternavn fra koden

Appversjon: 3.0.0

DB versjon:v3

Kommentar

Merk per.: Tilsynelatende, i den opprinnelige artikkelen kopierte forfatteren feilaktig teksten til denne blokken fra trinn 2. På dette trinnet bør det gjøres endringer i applikasjonskoden med sikte på å fjerne funksjonaliteten som bruker kolonnen last_name.

Ved å legge til en ny kolonne og kopiere innholdet, skapte vi bakoverkompatible databaseendringer. Dessuten, hvis vi ruller tilbake JAR eller har en gammel JAR i gang, vil den ikke gå i stykker under utførelse.

Tilbakeføring av søknad

For øyeblikket har vi appversjon 3.0.0 og database v3. Versjon 3.0.0 lagrer ikke data til last_name. Dette betyr at i surname den mest oppdaterte informasjonen lagres.

Trinn:

  1. rulle tilbake applikasjonen til versjon 2.0.0.
  2. versjon 2.0.0 bruker og last_name и surname.
  3. versjon 2.0.0 tar surname, hvis det ikke er null, ellers -last_name

Databaseendringer

Det er ingen strukturelle endringer i databasen. Følgende skript kjøres for å utføre den endelige migreringen av de gamle dataene:

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

Kodeendringer

Merk per.: Beskrivelsen av denne blokken ble også feilaktig kopiert av forfatteren fra trinn 2. I samsvar med artikkelens logikk bør endringer i koden på dette trinnet være rettet mot å fjerne elementer fra den som fungerer med kolonnen last_name.

Vi lagrer data som last_name, og i surname. I tillegg leser vi fra spalten last_name, siden det er det mest relevante. Under distribusjonsprosessen kan noen forespørsler bli behandlet av en forekomst som ennå ikke er oppgradert.

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

Trinn 4: Fjerner etternavn fra databasen

Appversjon: 4.0.0

DB versjon: v4

Kommentar

På grunn av det faktum at versjonskoden 3.0.0 brukte ikke kolonnen last_name, ingenting vondt vil skje under utførelse hvis vi ruller tilbake til 3.0.0 etter å ha fjernet en kolonne fra databasen.

Logger for utførelse av skript

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"}

DB endringer

omtrent v3 vi fjerner bare kolonnen last_name og legg til manglende restriksjoner.

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

Kodeendringer

Det er ingen endringer i koden.

Utgang

Vi har implementert en bakover-inkompatibel kolonnenavnendring ved å utføre flere bakoverkompatible distribusjoner. Nedenfor er en oppsummering av handlingene som er utført:

  1. distribusjon av applikasjonsversjon 1.0.0 с v1 databaseskjema (kolonnenavn = last_name)
  2. distribusjon av applikasjonsversjon 2.0.0, som lagrer data i last_name и surname. Søknaden leser fra last_name. Databasen er i versjon v2som inneholder kolonner som last_nameOg surname. surname er en kopi av last_name. (MERK: Denne kolonnen må ikke ha en ikke null-begrensning)
  3. distribusjon av applikasjonsversjon 3.0.0, som bare lagrer data i surname og leser fra etternavn. Når det gjelder databasen, finner den siste migreringen sted last_name в surname. Også en begrensning IKKE NULL fjernet fra last_name. Databasen er nå i versjon v3
  4. distribusjon av applikasjonsversjon 4.0.0 - ingen endringer er gjort i koden. Database distribusjon v4, som fjerner last_name. Her kan du legge til eventuelle manglende begrensninger til databasen.

Ved å følge denne tilnærmingen kan du alltid rulle tilbake én versjon uten å bryte database-/applikasjonskompatibiliteten.

Kode

All kode som brukes i denne artikkelen er tilgjengelig på Github. Nedenfor er tilleggsbeskrivelse.

prosjekter

Etter kloning av depotet, vil du se følgende mappestruktur.

├── 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)

skript

Du kan kjøre skriptene beskrevet i skriptene nedenfor, som vil demonstrere bakoverkompatible og inkompatible endringer i databasen.

For å se tilfellet med bakoverkompatible endringer, løpe:

./scripts/scenario_backward_compatible.sh

Og å se tilfelle med bakover uforenlige endringer, løpe:

./scripts/scenario_backward_incompatible.sh

Eksempel på fjærstøvel

Alle eksempler er hentet fra Spring Boot Sample Flyway.

Du kan ta en titt på http://localhost:8080/flyway, det er en liste over skript.

Dette eksemplet inkluderer også H2-konsollen (kl http://localhost:8080/h2-console) slik at du kan se databasestatusen (standard jdbc URL er jdbc:h2:mem:testdb).

i tillegg

Les også andre artikler på bloggen vår:

Kilde: www.habr.com

Legg til en kommentar