Implementarea fără timpi de nefuncționare și bazele de date

Implementarea fără timpi de nefuncționare și bazele de date

În acest articol, vom explica în detaliu cum să rezolvați problemele legate de compatibilitatea bazelor de date în timpul desfășurării. Vă vom arăta ce se poate întâmpla cu aplicațiile dumneavoastră în producție dacă încercați să realizați desfășurarea fără pregătiri prealabile. Apoi, vom parcurge etapele ciclului de viață al aplicației necesare pentru a avea un timp de nefuncționare nul (notă de traducere: mai departe — zero downtime). Rezultatul acțiunilor noastre va fi aplicarea unei modificări a bazei de date incompatibile înapoi într-un mod compatibil.

Dacă doriți să explorați exemplele de cod din articol, le veți găsi pe GitHub.

Introducere

Zero downtime deployment

Ce este acest concept misterios zero downtime deployment? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.

Cum să realizăm asta? Există mai multe metode, iar una dintre ele este:

  • desfășurați versiunea nr. 1 a serviciului dumneavoastră
  • efectuați migrarea bazei de date
  • desfășurați versiunea nr. 2 a serviciului dumneavoastră în paralel cu versiunea nr. 1
  • de îndată ce observați că versiunea nr. 2 funcționează cum trebuie, eliminați versiunea nr. 1
  • gata!

Ușor, nu-i așa? Din păcate, nu este atât de simplu, iar mai târziu vom analiza acest lucru în detaliu. Dar acum, să verificăm un alt proces de desfășurare destul de comun — blue green deployment.

Ați auzit vreodată de blue green deployment? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на acest articol, unde descriem acest lucru mai detaliat. Pe scurt, să recapitulăm cum se face blue green deployment:

  • asigurați funcționarea a două copii ale codului dumneavoastră de producție („blue” și „green”);
  • redirecționați tot traficul către mediu blue, adică URL-urile de producție să indice acolo;
  • desfășurați și testați toate modificările aplicației în mediu green;
  • comutați URL-urile de la blue la mediu green

Blue green deployment este o abordare care vă permite să introduceți noi funcții ușor, fără a vă face griji că producția se va întrerupe. Aceasta se datorează faptului că, chiar dacă se întâmplă ceva, puteți reveni cu ușurință la mediu anterior, pur și simplu prin „apăsarea unui buton”.

După ce ați citit tot ce a fost menționat mai sus, s-ar putea să vă întrebați: Ce legătură are zero downtime cu desfășurarea blue green?

Ei bine, au multe în comun, deoarece susținerea a două copii ale aceluiași mediu necesită dublu efort pentru întreținerea acestora. De aceea, unele echipe, așa cum afirmă Martin Fowler, se aleg cu o variantă a acestei abordări:

o altă variantă constă în utilizarea aceleași baze de date, creând comutatoare blue-green pentru straturile web și de domeniu. Într-un astfel de mod, bazele de date pot deveni adesea o problemă, în special când trebuie să modifici schema pentru a sprijini o nouă versiune a software-ului.

Și aici ajungem la problema principală din acest articol. Baza de date. Să aruncăm încă o dată o privire asupra acestei fraze.

efectuați migrarea bazei de date.

Acum trebuie să vă puneți întrebarea – ce se întâmplă dacă modificarea bazei de date devine incompatibilă? Oare prima versiune a aplicației mele nu se va strica? De fapt, exact asta se va întâmpla...

Astfel, chiar dacă există avantaje uriașe ale zero downtime / blue green deployment, companiile tind să urmeze următorul proces de desplegare a aplicațiilor mai sigur:

  • pregătiți un pachet cu noua versiune a aplicației
  • oprirea aplicației în execuție
  • rularea scripturilor pentru migrarea bazei de date
  • desplearea și pornirea noii versiuni a aplicației

În acest articol vom detalia cum puteți lucra cu baza de date și codul pentru a beneficia de avantajele desplegării fără timp de inactivitate.

Probleme cu baza de date

Dacă aveți o aplicație stateless, care nu stochează date în baza de date, puteți obține zero downtime deployment imediat. Din păcate, majoritatea software-ului trebuie să stocheze date undeva. De aceea trebuie să te gândești de două ori înainte de a face orice modificări în schema. Înainte de a ne aprofunda în detalii despre cum să modificăm schema astfel încât să se poată realiza desplegarea fără timp de inactivitate, să ne concentrăm întâi pe schema de gestionare a versiunilor.

Schema de gestionare a versiunilor

În acest articol vom folosi Flyway ca instrument pentru gestionarea versiunilor (n.tr.: este vorba despre migrarea bazelor de date). Evident, vom scrie de asemenea o aplicație Spring Boot, care are suport încorporat pentru Flyway și va efectua migrarea schemei în timpul configurării contextului aplicației. Când folosiți Flyway, puteți stoca scripturile de migrare în folderul proiectelor dvs. (în mod implicit în classpath:db/migration). Aici puteți vedea un exemplu de astfel de fișiere de migrare

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

În acest exemplu, vedem 4 scenarii de migrare care, dacă nu au fost efectuate anterior, vor fi executate unul după altul la pornirea aplicației. Să examinăm unul dintre fișiere (V1__init.sql) ca exemplu.

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

Totul vorbește de la sine: puteți folosi SQL pentru a defini cum ar trebui să fie modificată baza dumneavoastră de date. Pentru mai multe informații despre Spring Boot și Flyway, consultați Documentele Spring Boot.

Folosind instrumentul de gestionare a versiunilor împreună cu Spring Boot, obțineți 2 avantaje mari:

  • separarea modificărilor bazei de date de modificările codului
  • migrarea bazei de date are loc împreună cu implementarea aplicației dumneavoastră, adică procesul de desfășurare este simplificat

Rezolvarea problemelor cu baza de date

În secțiunea următoare a articolului, ne vom concentra pe examinarea a două abordări ale modificărilor bazei de date.

  • incompatibilitatea inversă
  • compabilitatea inversă

Primul va fi tratat ca un avertisment că nu ar trebui să faceți implementări fără timpi de nefuncționare fără o pregătire prealabilă… Al doilea oferă o soluție pentru cum să efectuați implementarea fără pauze și în același timp să mențineți compabilitatea inversă.

Proiectul nostru, la care vom lucra, va fi o aplicație simplă Spring Boot Flyway, care conține Person de first_name și last_name în baza de date (notă de traducere: Person este tabelul, iar first_name și last_name — este câmpul din acesta). Vrem să renunțăm la numele last_name în surname.

Presupoziții

Înainte de a ne aprofunda în detalii, este necesar să stabilim câteva presupuneri referitoare la aplicațiile noastre. Rezultatul principal pe care dorim să-l atingem va fi un proces destul de simplu.

Notă. PRO-TIP pentru Afaceri. Simplificarea proceselor vă poate economisi mulți bani la întreținere (cu cât mai mulți oameni lucrează în compania dumneavoastră, cu atât mai mulți bani puteți economisi)!

Nu trebuie să faceți rollback la baza de date

Aceasta simplifică procesul de desfășurare (unele rollback-uri de baze de date sunt practic imposibile, de exemplu, rollback-ul ștergerii). Preferăm să facem rollback doar la aplicații. Astfel, chiar dacă aveți baze de date diferite (de exemplu, SQL și NoSQL), pipeline-ul dumneavoastră de desfășurare va arăta la fel.

Trebuie să existe întotdeauna posibilitatea de a face rollback aplicației la o versiune anterioară (nu mai mult)

Rollback should only be performed when necessary. If the current version has a bug that is difficult to fix, we must be able to return to the last working version. We assume that this last working version is the previous one. Maintaining compatibility for code and database across multiple deployments would be extremely challenging and costly.

Notă. For better readability, within this article, we will change the major version of the application.

Pasul 1: Starea inițială

Versiunea aplicației: 1.0.0
Versiunea Bazei de Date: v1

Comentariu

Aceasta va fi starea inițială a aplicației.

Modificări ale Bazei de Date

BD conține 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');

Modificări ale codului

Aplicația salvează datele Person în 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
                + "]";
    }
}

Renaming de tip backward incompatible al coloanei

Hai să luăm un exemplu despre cum să schimbăm numele coloanei:

Atenție. Următorul exemplu va provoca intenționat o eroare. Facem acest lucru pentru a demonstra problema de compatibilitate a bazei de date.

Versiunea aplicației: 2.0.0.BAD

Versiunea Bazei de Date: v2bad

Comentariu

Modificările actuale NU ne permit să rulăm două instanțe (veche și nouă) simultan. Astfel, desfășurarea fără downtime va fi greu de realizat (dacă luăm în considerare presupunerile, este practic imposibil).

A/B Testing

Situația actuală este că avem o aplicație de versiune 1.0.0, desfasurată în producție, și BD v1. Trebuie să desfășurăm o a doua instanță a aplicației, versiunea 2.0.0.BAD, și să actualizăm baza de date la v2bad.

Pași:

  1. s-a desfășurat o nouă instanță a aplicației versiunea 2.0.0.BAD, care actualizează baza de date la v2bad
  2. în baza de date v2bad coloana last_name nu mai există — a fost schimbată în surname
  3. actualizarea bazei de date și a aplicației a fost efectuată cu succes, iar unele instanțe funcționează în 1.0.0, altele în 2.0.0.BAD. Toate sunt conectate la BD v2bad
  4. toate instanțele versiunii 1.0.0 vor începe să returneze erori deoarece vor încerca să insereze date în coloana last_name, care nu mai există
  5. toate instanțele versiunii 2.0.0.BAD vor funcționa fără probleme

După cum vedem, dacă facem modificări de tip backward incompatible în BD și aplicație, A/B testing-ul devine imposibil.

Rollback aplicației

Să presupunem că după încercarea de a efectua desfășurarea A/B (notă de traducere: probabil că autorul s-a referit la A/B testing) am decis că trebuie să revenim aplicația la versiunea 1.0.0. Să presupunem că nu vrem să facem un rollback al bazei de date.

Pași:

  1. oprim instanța aplicației versiunea 2.0.0.BAD
  2. baza de date este încă v2bad
  3. deoarece versiunea 1.0.0 nu înțelege ce este surname, vom vedea erori
  4. iadul s-a dezlănțuit, nu mai putem reveni

După cum vedeți, dacă facem modificări incompatibile înapoi între baza de date și aplicație, nu putem reveni la versiunea anterioară.

Jurnalele de execuție a scriptului

Scenariul incompatibil înapoi:

01) Rulați 1.0.0
02) Așteptați ca aplicația (1.0.0) să pornească
03) Generați o persoană apelând POST localhost:9991/person pentru versiunea 1.0.0
04) Rulați 2.0.0.BAD
05) Așteptați ca aplicația (2.0.0.BAD) să pornească
06) Generați o persoană apelând POST localhost:9991/person pentru versiunea 1.0.0 < -- aceasta ar trebui să eșueze
07) Generați o persoană apelând POST localhost:9992/person pentru versiunea 2.0.0.BAD < -- aceasta ar trebui să reușească

Pornind aplicația în versiunea 1.0.0
Generați o persoană în versiunea 1.0.0
Trimiteți un post la 127.0.0.1:9991/person. Acesta este răspunsul:

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

Pornind aplicația în versiunea 2.0.0.BAD
Generați o persoană în versiunea 1.0.0
Trimiteți un post la 127.0.0.1:9991/person. Acesta este răspunsul:

curl: (22) URL-ul solicitat a returnat eroare: 500 Internal Server Error

Generați o persoană în versiunea 2.0.0.BAD
Trimiteți un post la 127.0.0.1:9995/person. Acesta este răspunsul:

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

Modificări ale Bazei de Date

Scriptul de migrare care redenumește last_name în surname

Scriptul Flyway original:

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

Scriptul care redenumește last_name.

-- Această schimbare este incompatibilă înapoi - nu poți face testare A/B
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Modificări ale codului

Am schimbat numele câmpului lastName pe surname.

Redenumirea coloanei într-un mod compatibil înapoi

Aceasta este cea mai frecventă situație cu care ne putem confrunta. Trebuie să facem modificări incompatibile înapoi. Am dovedit deja că pentru implementări fără downtime nu trebuie să aplicăm pur și simplu migrarea bazei de date fără acțiuni suplimentare. În această secțiune a articolului vom face 3 implementări ale aplicației împreună cu migrarea bazei de date, pentru a obține rezultatul dorit și, în același timp, a păstra compatibilitatea înapoi.

Notă. Să ne amintim că avem o bază de date de versiune v1. Aceasta conține coloane first_name și last_name. Trebuie să modificăm last_name pe surname. De asemenea, avem aplicația de versiune 1.0.0, care deocamdată nu utilizează surname.

Pasul 2: Adăugăm surname

Versiunea aplicației: 2.0.0
Versiunea Bazei de Date: v2

Comentariu

Adăugând o nouă coloană și copind conținutul acesteia, creăm modificări compatibile înapoi în baza de date. În același timp, dacă facem rollback JAR sau avem un JAR vechi funcțional, nu se va strica în timpul execuției.

Implementăm o nouă versiune

Pași:

  1. efectuați migrarea bazei de date pentru a crea noua coloană surname. Acum baza dumneavoastră de date de versiune v2
  2. copiați datele din last_name în surname. Vă rugăm să observați, ceea ce, dacă aveți multe dintre aceste date, ar trebui să luați în considerare migrarea în loturi!
  3. scrieți codul în care se folosesc AM BOTH și nou, și vechi coloană. Acum aplicația dvs. versiunea 2.0.0
  4. citiți valoarea din coloană surname, dacă nu null, sau din last_name, dacă surname nu este specificat. Puteți elimina getLastName() din cod, deoarece el va returna null la revenirea aplicației dvs. de la 3.0.0 la 2.0.0.

Dacă folosiți Spring Boot Flyway, acești doi pași se vor executa în timpul pornirii versiunii 2.0.0 aplicației. Dacă lansați instrumentul de gestionare a versiunilor bazei de date manual, va trebui să faceți două acțiuni diferite (mai întâi să actualizați versiunea bazei de date manual, apoi să desfășurați o nouă aplicație).

Este important. Amintiți-vă că noua coloană NU TREBUIE fii NOT NULL. Dacă faceți revenire, aplicația veche nu știe despre noua coloană și nu o va seta în timpul Insert. Dar dacă adăugați această constrângere, iar baza de date va fi v2, va necesita setarea valorii noii coloane. Ceea ce va duce la încălcări ale constrângerilor.

Este important. Ar trebui să eliminați metoda getLastName(), deoarece în versiunea 3.0.0 din cod nu există conceptul de coloană last_name. Aceasta înseamnă că vor fi setate valori nule. Puteți păstra metoda și adăuga verificări pe null, dar o soluție mult mai bună ar fi să vă asigurați că în logica getSurname() ați ales o valoare corectă non-nulă.

A/B Testing

Situația actuală este că avem o aplicație de versiune 1.0.0, desfășurată pe producție, și baza de date în v1. Trebuie să desfășurăm o a doua instanță a aplicației versiunii 2.0.0, care va actualiza baza de date până la v2.

Pași:

  1. s-a desfășurat o nouă instanță a aplicației versiunea 2.0.0, care actualizează baza de date la v2
  2. între timp, unele cereri au fost procesate de instanțele versiunii 1.0.0
  3. actualizarea a fost finalizată cu succes și aveți câteva instanțe funcționale ale aplicației versiunii 1.0.0 și celelalte versiuni 2.0.0. Toate comunică cu baza de date în v2
  4. versiune 1.0.0 nu utilizează în baza de date coloana surname, iar versiunea 2.0.0 o folosește. Ele nu se perturbă reciproc și nu ar trebui să fie erori.
  5. versiune 2.0.0 salvează date atât în coloana veche, cât și în cea nouă, ceea ce asigură compatibilitatea inversă

Este important. Dacă aveți vreo cerere care calculează elemente pe baza valorilor din coloana veche / nouă, trebuie să rețineți că acum aveți duplicarea valorilor (cel mai probabil, ele migrează încă). De exemplu, dacă doriți să numărați utilizatorii al căror nume de familie (indiferent cum se numește colona) începe cu litera A, atunci până la finalizarea migrației datelor (old → nou coloana) puteți avea date inconsistente dacă efectuați o cerere la noua coloană.

Rollback aplicației

Acum avem aplicația versiunii 2.0.0 și baza de date în v2.

Pași:

  1. revină aplicația dvs. la versiunea 1.0.0.
  2. versiune 1.0.0 nu folosește coloană în baza de date surname, prin urmare rollback-ul ar trebui să fie reușit

Modificări DB

Baza de date conține o coloană numită last_name.

Scriptul inițial 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 de adăugare surname.

Atenție. Amintiți-vă că NU TREBUIE SĂ ADĂUGAȚI restricții NOT NULL în coloana adăugată. Dacă faceți rollback la JAR, vechea versiune nu are cunoștință despre coloana adăugată și va seta automat valoarea NULL. În cazul în care există o astfel de restricție, vechea aplicație se va defecta pur și simplu.

-- NOTĂ: Acest câmp nu poate avea restricția NOT NULL pentru că, dacă faceți rollback, vechea versiune nu va cunoaște acest câmp
-- și va seta întotdeauna valoarea la NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- PRESUPUNEM CĂ ESTE O MIGRARE RAPIDĂ - ALTFEL AR TREBUI SĂ MIGRĂM ÎN BATCH-uri
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Modificări ale codului

Salvăm datele atât în last_name, cât și în surname. În același timp, citim din last_name, deoarece această coloană este cea mai relevantă. În timpul desfășurării, unele interogări ar fi putut fi procesate de o instanță a aplicației care nu a fost încă actualizată.

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

Pasul 3: Eliminarea last_name din cod

Versiunea aplicației: 3.0.0

Versiunea Bazei de Date:v3

Comentariu

Nota editorului: Se pare că în articolul inițial textul acestui bloc a fost copiat din greșeală de către autor din pasul 2. În acest pas ar trebui efectuate modificări în codul aplicației, vizând eliminarea funcționalității care utilizează coloana last_name.

Adăugând o nouă coloană și copierea conținutului acesteia, am creat modificări compatibile în baza de date. De asemenea, dacă revenim la JAR sau avem un JAR vechi funcțional, nu se va defecta în timpul execuției.

Rollback aplicației

În prezent avem aplicația versiunea 3.0.0 și baza de date v3. Versiunea 3.0.0 nu salvează datele în last_name. Aceasta înseamnă că în surname se stochează cele mai recente informații.

Pași:

  1. revină aplicația dvs. la versiunea 2.0.0.
  2. versiune 2.0.0 folosește și last_name și surname.
  3. versiune 2.0.0 va lua surname, dacă nu este nul, altfel -last_name

Modificări ale Bazei de Date

Nu există modificări structurale în baza de date. Se execută următorul script, care efectuează migrarea finală a datelor vechi:

-- PRESUPUNEM CĂ ESTE O MIGRARE RAPIDĂ - ALTFEL AR TREBUI SĂ MIGRĂM ÎN BATCH-uri
-- DE ASISTEM, NU VERIFICĂM DACĂ NU SUPRASCRIEM ÎNREGISTRĂRI EXISTENTE. AR TREBUI SĂ COMPARĂM
-- VERSIUNILE ÎNREGISTRĂRII PENTRU A ASIGURA CĂ DACĂ EXISTĂ DEJA O ÎNREGISTRARE CU UN NUMĂR DE VERSIUNE MAI MARE
-- NU O SĂ SUPRASCRIEM.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- RENUNȚÂND LA RESTRICȚIA NOT NULL; ALTFEL VEI ÎNCERCA SĂ INSEREZI O VALOARE NULL A LAST_NAME
-- CU O RESTRICȚIE NOT_NULL.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Modificări ale codului

Nota editorului: Descrierea acestui bloc a fost, de asemenea, copiată greșit de către autor din pasul 2. Conform logicii narațiunii articolului, modificările din cod în acest pas ar trebui să fie orientate spre eliminarea elementelor care interacționează cu coloana last_name.

Păstrăm datele atât în last_name, cât și în surname. În plus, citim din coloana last_name, deoarece aceasta este cea mai relevantă. În timpul desfășurării, unele cereri pot fi procesate de o instanță care nu a fost încă actualizată.

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

Pasul 4: Eliminarea last_name din Bază de Date

Versiunea aplicației: 4.0.0

Versiunea Bazei de Date: v4

Comentariu

Din cauza faptului că codul versiunii 3.0.0 nu a folosit coloana last_name, în timpul execuției nu se va întâmpla nimic rău dacă revenim la 3.0.0 după eliminarea coloanei din baza de date.

Jurnalele de execuție a scriptului

O vom face în următorul mod:

01) Rulați 1.0.0
02) Așteptați ca aplicația (1.0.0) să pornească
03) Generați o persoană apelând POST localhost:9991/person pentru versiunea 1.0.0
04) Rulați 2.0.0
05) Așteptați ca aplicația (2.0.0) să pornească
06) Generați o persoană apelând POST localhost:9991/person pentru versiunea 1.0.0
07) Generați o persoană apelând POST localhost:9992/person pentru versiunea 2.0.0
08) Opriți aplicația (1.0.0)
09) Rulați 3.0.0
10) Așteptați ca aplicația (3.0.0) să pornească
11) Generați o persoană apelând POST localhost:9992/person pentru versiunea 2.0.0
12) Generați o persoană apelând POST localhost:9993/person pentru versiunea 3.0.0
13) Opriți aplicația (3.0.0)
14) Rulați 4.0.0
15) Așteptați ca aplicația (4.0.0) să pornească
16) Generați o persoană apelând POST localhost:9993/person pentru versiunea 3.0.0
17) Generați o persoană apelând POST localhost:9994/person pentru versiunea 4.0.0

Pornind aplicația în versiunea 1.0.0
Generarea unei persoane în versiunea 1.0.0
Trimițând o postare la 127.0.0.1:9991/person. Acesta este răspunsul:

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

Pornind aplicația în versiunea 2.0.0

Generarea unei persoane în versiunea 1.0.0
Trimițând o postare la 127.0.0.1:9991/person. Acesta este răspunsul:

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

Generarea unei persoane în versiunea 2.0.0
Trimițând o postare la 127.0.0.1:9992/person. Acesta este răspunsul:

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

Oprind aplicația 1.0.0

Pornind aplicația în versiunea 3.0.0

Generarea unei persoane în versiunea 2.0.0
Trimițând o postare la 127.0.0.1:9992/person. Acesta este răspunsul:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Generarea unei persoane în versiunea 3.0.0
Trimițând o postare la 127.0.0.1:9993/person. Acesta este răspunsul:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Oprind aplicația 2.0.0

Pornind aplicația în versiunea 4.0.0

Generarea unei persoane în versiunea 3.0.0
Trimițând o postare la 127.0.0.1:9993/person. Acesta este răspunsul:

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

Generarea unei persoane în versiunea 4.0.0
Trimițând o postare la 127.0.0.1:9994/person. Acesta este răspunsul:

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

Modificările Bazei de Date

În ceea ce privește v3 pur și simplu eliminăm coloana last_name și adăugăm constrângerile lipsă.

-- ÎNLOCUIȚI COLOANA
ALTER TABLE PERSON DROP last_name;

-- ADĂUGAȚI CONSTRÂNGERI
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Modificări ale codului

Nu există modificări în cod.

Ieșire

Am aplicat cu succes modificarea incompatibilă a numelui coloanei, realizând mai multe desfășurări compatibile. Mai jos este un rezumat al acțiunilor efectuate:

  1. desfășurarea aplicației versiunii 1.0.0 de v1 schema Bazei de Date (numele coloanei = last_name)
  2. desfășurarea aplicației versiunii 2.0.0, care păstrează datele în last_name și surname. Aplicația citește din last_name. Baza de date este în versiunea v2, care conține coloane precum last_name, sau surname. surname este o copie a last_name. (NOTĂ: această coloană nu ar trebui să aibă restricția not null)
  3. desfășurarea aplicației versiunii 3.0.0, care stochează datele doar în surname și citește din surname. Cât despre baza de date, are loc ultima migrație last_name în surname. De asemenea, restricția NOT NULL este ridicată de la last_name. Baza de date este acum în versiunea v3
  4. desfășurarea aplicației versiunii 4.0.0 — nu se fac modificări în cod. Implementarea bazei de date v4, care șterge last_name. Aici puteți adăuga orice restricții lipsă în baza de date.

Urmând această abordare, puteți întotdeauna să reveniți la o versiune anterioară fără a afecta compatibilitatea bazei de date / aplicației.

Cod

Întregul cod folosit în acest articol este disponibil pe Github. Mai jos se află o descriere suplimentară.

Proiecte

După clonarea depozitului, veți vedea următoarea structură de foldere.

├── boot-flyway-v1              - versiunea 1.0.0 a aplicației cu v1 a schemei
├── boot-flyway-v2              - versiunea 2.0.0 a aplicației cu v2 a schemei (compatibilă cu versiunile anterioare - aplicația poate fi revenită)
├── boot-flyway-v2-bad          - versiunea 2.0.0.BAD a aplicației cu v2bad a schemei (incompatibilă cu versiunile anterioare - aplicația nu poate fi revenită)
├── boot-flyway-v3              - versiunea 3.0.0 a aplicației cu v3 a schemei (aplicația poate fi revenită)
└── boot-flyway-v4              - versiunea 4.0.0 a aplicației cu v4 a schemei (aplicația poate fi revenită)

Scripturi

Puteți rula scripturile descrise în scripturile de mai jos, care vor demonstra modificări compatibile și incompatibile cu baza de date.

Pentru a vedea cazul cu modificări compatibile, rulați:

.\/scripts\/scenario_backward_compatible.sh

Și pentru a vedea cazul cu modificări incompatibile, rulați:

.\/scripts\/scenario_backward_incompatible.sh

Exemplu Spring Boot Flyway

Toate exemplele sunt preluate de la Exemplu Spring Boot Flyway.

Puteți viziona http://localhost:8080/flyway, acolo este lista de scripturi.

Acest exemplu include și consola H2 (la adresa http://localhost:8080/h2-console), pentru a putea vizualiza starea bazei de date (URL jdbc implicit — jdbc:h2:mem:testdb).

În plus

Citiți și alte articole de pe blogul nostru:

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster