Zero Downtime Deployment и бази данни

Zero Downtime Deployment и бази данни

В тази статия ще обясним подробно как да решите проблеми, свързани със съвместимостта на базите данни при внедряване. Ще разгледаме какво може да се случи с вашите приложения в продукция, ако се опитате да извършите внедряване без предварителна подготовка. След това ще минаваме през етапите на жизнения цикъл на приложението, които са необходими, за да имате нулево време на престой (забележка на преводача: по-нататък — zero downtime). Резултатът от нашите операции ще бъде прилагането на обратно несъвместима промяна на базата данни по обратим начин.

Ако искате да се запознаете с примери на код от статията, можете да ги намерите на GitHub.

Въведение

Zero downtime deployment

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

Как да го постигнем? Има няколко начина, един от които е:

  • разгърнете версия №1 на услугата си
  • извършете миграция на базата данни
  • разгърнете версия №2 на услугата си паралелно с версия №1
  • когато видите, че версия №2 работи както трябва, премахнете версия №1
  • готово!

Лесно, нали? За съжаление, не е така просто и по-късно ще разгледаме това по-подробно. А сега нека да разгледаме още един доста разпространен процес на внедряване — blue green deployment.

Чували ли сте някога за blue green deployment? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на тази статия, където описваме това по-подробно. В кратце, нека напомня как да правим blue green deployment:

  • осигурете функционирането на две копия на кода на вашата продукция („синя“ и „зелена“);
  • насочете целия трафик към синята среда, т.е. така че URL адресите на продукцията да сочат натам;
  • разгръщайте и тествате всички промени в приложението в зелената среда;
  • превключете URL адресите от синята на зелената среда

Blue green deployment е подход, който ви позволява лесно да въвеждате нови функции, без да се притеснявате, че продукцията ще се счупи. Това се дължи на факта, че дори ако нещо се случи, можете лесно да се върнете към предишната среда, просто като 'включите превключвателя'.

Прочитайки всичко изброено по-горе, може да зададете въпроса: Какво общо има zero downtime с blue green внедряване?

Т well, те имат доста общо, тъй като поддържането на две копия на същата среда изисква двойни усилия за тяхното обслужване. Ето защо някои екипи, както твърди Martin Fowler, се придържат към вариация на този подход:

Другият вариант е да се използва същата база данни, създавайки синьо-зелени превключватели за уеб и домейн слоевете. В този подход базите данни често могат да бъдат проблем, особено когато трябва да промените схемата й, за да поддържате нова версия на софтуера.

И тук стигаме до основния проблем в тази статия. База данни. Нека отново погледнем тази фраза.

извършете миграция на базата данни.

Сега трябва да си зададете въпроса – какво, ако обратната промяна на базата данни е несъвместима? Няма ли да се разпадне първата версия на приложението ми? Всъщност, точно това ще се случи…

Следователно, дори с огромните предимства на zero downtime / blue green deployment, компаниите са склонни да следват следния по-безопасен процес за деплой на своите приложения:

  • подгответе пакет с новата версия на приложението
  • изключете стартираното приложение
  • стартирайте скриптовете за миграция на базата данни
  • развернете и стартирайте новата версия на приложението

В тази статия подробно ще опишем как можете да работите с базата данни и кода, за да се възползвате от предимствата на zero downtime deployment.

Проблеми с базата данни

Ако имате stateless приложение, което не съхранява данни в базата данни, можете да получите zero downtime deployment веднага. За съжаление, голяма част от софтуера трябва да съхранява данни някъде. Затова трябва два пъти да помислите, преди да направите някакви промени в схемата. Преди да се задълбочим в подробностите как да променим схемата по такъв начин, че да стане възможен деплой без простои, нека първо се фокусираме върху схемата за управление на версиите.

Схема за управление на версиите

В тази статия ще използваме Flyway като инструмент за управление на версиите (бележка на преводача: става въпрос за миграции на базата данни). Естествено, ще напишем и приложение на Spring Boot, което има вградена поддръжка за Flyway и ще извърши миграция на схемата по време на настройването на контекста на приложението. При използване на Flyway можете да съхранявате скриптовете за миграция в папката на вашите проекти (по подразбиране в classpath:db/migration). Тук можете да видите пример за такива файлове за миграция

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

В този пример виждаме 4 сценария за миграция, които, ако не са били изпълнени по-рано, ще се изпълняват един след друг при стартиране на приложението. Нека разгледаме един от файловете (V1__init.sql) за пример.

СЪЗДАЙ ТАБЛИЦА PERSON (
id BIGINT ГЕНЕРИРАН ПО ИЗМЕСТВАНЕ КАТО ИДЕНТИЧЕН,
first_name varchar(255) not null,
last_name varchar(255) not null
);

вмъкни в PERSON (first_name, last_name) стойности ('Дейв', 'Сайер');

Всичко прекрасно говори само за себе си: можете да използвате SQL, за да определите как трябва да бъде променена вашата база данни. За повече информация относно Spring Boot и Flyway се запознайте с Документация на Spring Boot.

Чрез инструмента за управление на версии със Spring Boot получавате 2 големи предимства:

  • разделяте промените в базата данни от промените в кода
  • миграцията на базата данни става заедно с разгръщането на вашето приложение, т.е. вашият процес на внедряване се опростява

Решаване на проблеми с базата данни

В следващия раздел на статията ще се фокусираме върху разглеждането на два подхода към промените в базата данни.

  • обратна несъвместимост
  • обратна съвместимост

Първият ще бъде разгледан като предупреждение, че не трябва да се извършва разгръщане без време без прекъсване без предварителна подготовка… Вторият предлага решение как може да се извърши разгръщане без престои и едновременно да се поддържа обратна съвместимост.

Нашият проект, върху който ще работим, ще бъде просто приложение Spring Boot Flyway, в което има Person с first_name и last_name в базата данни (прим. пер.: Person е таблица, а first_name и last_name е полето в нея). Искаме да преименуваме last_name в surname.

Допускания

Преди да се задълбочим в детайлите, е необходимо да обозначим няколко допускания относно нашите приложения. Основният резултат, който искаме да постигнем, ще бъде доста прост процес.

Забележка. Бизнес СЪВЕТ. Опростяването на процесите може да ви спести много пари за поддържане (колкото повече хора работят във вашата компания, толкова повече пари можете да спестите)!

Не трябва да извършвате откат на базата данни

Това опростява процеса на разгръщане (нSome откати на базата данни практически са невъзможни, например откат на изтриване). Предпочитаме да възстановяваме само приложенията. Така, дори ако имате различни бази данни (например SQL и NoSQL), вашият канал за внедряване ще изглежда еднакво.

Трябва да има възможност ЗА ВСЕГДА да се върне приложението с една версия назад (не повече)

Откатът трябва да се прави само при необходимост. Ако текущата версия съдържа грешка, която трудно може да бъде отстранена, трябва да имаме възможност да се върнем към последната работеща версия. Предполагаме, че тази последна работеща версия е предишната. Поддържането на съвместимост на кода и базата данни за повече от едно внедряване би било изключително трудно и скъпо.

Бележка. За по-добра четимост, в рамките на тази статия ще променим основната версия на приложението.

Стъпка 1: Изходно състояние

Версия на приложението: 1.0.0
Версия на БД: v1

Коментар

Това ще бъде изходното състояние на приложението.

Промени в БД

БД съдържа 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');

Промени в кода

Приложението запазва данни Person в 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
                + "]";
    }
}

Несъвместимо обратно преименуване на колона

Нека разгледаме пример как да променим името на колоната:

Внимание. Следващият пример умишлено ще доведе до счупване. Показваме го с цел да демонстрираме проблема със съвместимостта на базата данни.

Версия на приложението: 2.0.0.BAD

Версия на БД: v2bad

Коментар

Текущите промени НЕ ни позволяват да стартираме два екземпляра (стария и новия) едновременно. Следователно, внедряването без престой ще бъде трудно постижимо (ако се вземат предвид предположенията, то е практически невъзможно).

A/B тестване

Настоящата ситуация е такава, че имаме приложение версия 1.0.0, развито в продукция, и БД v1. Трябва да внедрим втори екземпляр на приложението, версия 2.0.0.BAD, и да обновим базата данни до v2bad.

Стъпки:

  1. развива се нов екземпляр на приложението версия 2.0.0.BAD, която обновява базата данни до v2bad
  2. в базата данни v2bad колона last_name вече не съществува — тя е променена на surname
  3. актуализацията на базата данни и приложението премина успешно, и някои екземпляри работят в 1.0.0, други — в 2.0.0.BAD. Всички са свързани с БД v2bad
  4. всички екземпляри версия 1.0.0 ще започнат да дават грешки, защото ще се опитат да вкарат данни в колона last_name, която вече я няма
  5. всички екземпляри версия 2.0.0.BAD ще работят без проблеми

Както виждате, ако направим несъвместими обратно промени в БД и приложението, A/B тестването е невъзможно.

Откат на приложението

Нека предположим, че след опит за A/B внедряване (бележка: вероятно тук авторът има предвид A/B тестване) решихме, че трябва да върнем приложението към версия 1.0.0. Да предположим, че не искаме да правим откат на базата данни.

Стъпки:

  1. спираме инстанцията на приложението версия 2.0.0.BAD
  2. базата данни все още v2bad
  3. тъй като версията 1.0.0 не разбира какво е surname, ще видим грешки
  4. адът е изплъзнал от контрол, не можем да се върнем назад

Както виждате, ако направим обратно несъвместими промени в БД и приложението, не можем да се върнем към предишната версия.

Логовете на изпълнение на скрипта

Обратно несъвместим сценарий:

01) Стартирайте 1.0.0
02) Изчакайте приложението (1.0.0) да се стартира
03) Генерирайте човек, като извикате POST localhost:9991/person на версия 1.0.0
04) Стартирайте 2.0.0.BAD
05) Изчакайте приложението (2.0.0.BAD) да се стартира
06) Генерирайте човек, като извикате POST localhost:9991/person на версия 1.0.0 <-- това трябва да се провали
07) Генерирайте човек, като извикате POST localhost:9992/person на версия 2.0.0.BAD <-- това трябва да премине

Стартиране на приложението в версия 1.0.0
Генериране на човек в версия 1.0.0
Изпращане на пост до 127.0.0.1:9991/person. Това е отговорът:

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

Стартиране на приложението в версия 2.0.0.BAD
Генериране на човек в версия 1.0.0
Изпращане на пост до 127.0.0.1:9991/person. Това е отговорът:

curl: (22) Исканото URL върна грешка: 500 Вътрешна сървърна грешка

Генериране на човек в версия 2.0.0.BAD
Изпращане на пост до 127.0.0.1:9995/person. Това е отговорът:

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

Промени в БД

Скрипт за миграция, който преименува last_name в surname

Изходен 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');

Скрипт, който преименува last_name.

-- Тази промяна е обратно несъвместима - не можете да правите A/B тестване
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Промени в кода

Променихме името на полето lastName на surname.

Преименуване на колона по обратно-совместим начин

Това е най-честата ситуация, с която можем да се сблъскаме. Трябва да направим обратно несъвместими промени. Вече доказахме, че за деплой без прекъсвания не трябва просто да прилагаме миграцията на базата данни без допълнителни действия. В тази част на статията ще извършим 3 деплоя на приложението заедно с миграции на базата данни, за да постигнем желаните резултати и в същото време да запазим обратна съвместимост.

Забележка. Нека припомним, че имаме БД версия v1. Тя съдържа колони first_name и last_name. Трябва да променим last_name на surname. Имаме също приложение версия 1.0.0, което все още не използва surname.

Стъпка 2: Добавяне на surname

Версия на приложението: 2.0.0
Версия на БД: v2

Коментар

Добавяйки нова колона и копирайки нейното съдържание, ние създаваме обратно съвместими промени в БД. В същото време, ако откатим JAR или разполагаме с работещ стар JAR, той няма да се провали по време на изпълнението.

Пускаме нова версия

Стъпки:

  1. извършете миграция на БД, за да създадете нова колона surname. Сега вашата БД версия v2
  2. копирайте данните от last_name в surname. Обърнете внимание, и ако имате много от тези данни, трябва да обмислите партидна миграция!
  3. напишете код, където се използват И ДВЕТЕ и нов, и старата колона. Сега вашето приложение версия 2.0.0
  4. прочетете стойността от колоната surname, ако не null, или от last_name, ако surname не е зададена. Можете да премахнете getLastName() от кода, тъй като той ще издаде null при възстановяване на вашето приложение с 3.0.0 до 2.0.0.

Ако използвате Spring Boot Flyway, тези две стъпки ще бъдат извършени при стартиране на версията 2.0.0 на приложението. Ако стартирате инструмента за управление на версии на базата данни ръчно, ще трябва да направите две различни действия (първо актуализирайте версията на bd ръчно, а след това разширете новото приложение).

Важно. Помнете, че новосъздадената колона НЕ ТРЯБВА да бъде NOT NULL. Ако правите възстановяване, старото приложение не знае за новата колона и няма да я установи по време на Insert. Но ако добавите това ограничение, и вашата БД ще бъде v2, това ще изисква задаване на стойност на новата колона. Което ще доведе до нарушения на ограниченията.

Важно. Трябва да премахнете метода getLastName(), тъй като в версия 3.0.0 в кода не съществува концепцията за колона last_name. Това означава, че там ще бъдат зададени null. Можете да оставите метода и да добавите проверки на null, но много по-добро решение би било да се уверите, че в логиката getSurname() избрахте правилната ненулева стойност.

A/B тестване

Настоящата ситуация е такава, че имаме приложение версия 1.0.0, разширено на проде, и БД в v1. Трябва да разширим втори екземпляр на приложението версия 2.0.0, който ще актуализира базата данни до v2.

Стъпки:

  1. развива се нов екземпляр на приложението версия 2.0.0, която обновява базата данни до v2
  2. през това време някои заявки бяха обработвани от екземплярите на версия 1.0.0
  3. актуализацията премина успешно, и имате няколко работещи екземпляра на приложението версия 1.0.0 и останалите версии 2.0.0. Всички комуникират с БД в v2
  4. версия 1.0.0 не използва в БД колоната surname, а версия 2.0.0 използва. Те не си пречат и не трябва да има грешки.
  5. версия 2.0.0 запазва данни както в старата, така и в новата колона, което осигурява обратно съвместимост

Важно. Ако имате някакви заявки, които броят елементи на базата на стойности от старата / новата колона, трябва да помните, че сега имате дублиране на стойности (вероятно все още мигрират). Например, ако искате да броите количеството потребители, чиято фамилия (каквото и да е името на колоната) започва с буквата A, то до завършването на миграцията на данните (oldнов колона) можете да имате несъответстващи данни, ако извършите заявка към новата колона.

Откат на приложението

В момента имаме приложение версия 2.0.0 и база данни в v2.

Стъпки:

  1. възстановете вашето приложение до версия 1.0.0.
  2. версия 1.0.0 не използва в БД колоната surname, затова откатът трябва да е успешен

Промени в DB

DB съдържа колона с име last_name.

Изходен скрипт 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');

Скрипт за добавяне surname.

Внимание. Запомнете, че не трябва да добавяте ограничения NOT NULL в добавената колона. Ако откатите JAR, старата версия няма да знае за добавената колона и автоматично ще зададе стойност NULL. При наличие на такова ограничение, старото приложение просто ще се счупи.

-- ЗАБЕЛЕЖКА: Тази колона не може да има ограничение NOT NULL, тъй като, ако откатим, старата версия няма да знае за тази колона
-- и винаги ще я задава на NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- ПРЕДПОЛАГАМЕ, ЧЕ ТОВА Е БЪРЗО МИГРИРАНЕ - ИНАЧЕ ЩЕ ПРИЛОЖИМ МИГРАЦИЯ НА ПАРТИДИ
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Промени в кода

Запазваме данните както в last_name, така и в surname. Четем от 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;
    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
                + "]";
    }
}

Стъпка 3: Премахване на last_name от кода

Версия на приложението: 3.0.0

Версия на БД:v3

Коментар

Прим. пер.: Очевидно, в оригиналната статия авторът погрешка е копирал текста на този блок от стъпка 2. На този етап трябва да се извършат промени в кодa на приложението, насочени към премахване на функционалността, която използва колоната last_name.

Чрез добавяне на нова колона и копиране на съдържанието ѝ, създадохме обратно съвместими промени в БД. Освен това, ако откатим JAR или имаме работещ стар JAR, той няма да се счупи по време на изпълнението.

Откат на приложението

В момента имаме приложение версия 3.0.0 и база данни v3. Версия 3.0.0 не запазва данни в last_name. Това означава, че в surname се съхранява най-актуалната информация.

Стъпки:

  1. възстановете вашето приложение до версия 2.0.0.
  2. версия 2.0.0 използва и last_name и surname.
  3. версия 2.0.0 взима surname, ако не е нулев, в противен случай -last_name

Промени в БД

В БД няма структурни промени. Изпълнява се следният скрипт, който извършва окончателна миграция на стари данни:

-- ПРЕДПОЛАГАМЕ, ЧЕ ТОВА Е БЪРЗО МИГРИРАНЕ - ИНАЧЕ ЩЕ ПРИЛОЖИМ МИГРАЦИЯ НА ПАРТИДИ
-- СЪЩО ТАКА НЕ ПРОВЕРОВАМЕ ДАЛИ НЕ ПРЕПИСВАМЕ СЪЩЕСТВУВАЩИ ЗАПИСИ. ТРЯБВА ДА СРАВНИМ
-- ВЕРСИИТЕ ЗА ЗАПИСИ, ЗА ДА СЕ УБЕДИМ, ЧЕ, АКО ИМА ВЕЧЕ ЗАПИС С ПО-ВИСОК НОМЕР НА ВЕРСИЯ
-- НЯМА ДА ГО ПРЕПИШЕМ.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- ПОКУПУВАМЕ ОГРАНИЧЕНИЕТО NOT NULL; В ПРОТИВЕН СЛУЧАЙ ЩЕ ПРЕДПОЛАГАMЕ, ЧЕ ЩЕ СЕ ОПИТАТЕ ДА ВКЛЮЧИТЕ NULL СТОЙНОСТ НА LAST_NAME
-- С ОГРАНИЧЕНИЕ NOT NULL.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Промени в кода

Прим. пер.: Описанието на този блок също е погрешно копирано от автора от стъпка 2. В съответствие с логиката на разказването в статията, промените в кода на този етап трябва да са насочени към премахване на елементите, които работят с колоната last_name.

Запазваме данните както в last_name, така и в surname. Освен това, четем от колоната 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 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
                + "]";
    }
}

Стъпка 4: Премахване на last_name от БД

Версия на приложението: 4.0.0

Версия на БД: v4

Коментар

Поради факта, че кодът на версията 3.0.0 не е използвал колоната last_name, по време на изпълнението нищо лошо няма да се случи, ако се върнем към 3.0.0 след премахването на колоната от база данни.

Логовете на изпълнение на скрипта

Ще го направим по следния начин:

01) Стартирайте 1.0.0
02) Изчакайте приложението (1.0.0) да се стартира
03) Генерирайте човек, като извикате POST localhost:9991/person за версия 1.0.0
04) Стартирайте 2.0.0
05) Изчакайте приложението (2.0.0) да се стартира
06) Генерирайте човек, като извикате POST localhost:9991/person за версия 1.0.0
07) Генерирайте човек, като извикате POST localhost:9992/person за версия 2.0.0
08) Убийте приложението (1.0.0)
09) Стартирайте 3.0.0
10) Изчакайте приложението (3.0.0) да се стартира
11) Генерирайте човек, като извикате POST localhost:9992/person за версия 2.0.0
12) Генерирайте човек, като извикате POST localhost:9993/person за версия 3.0.0
13) Убийте приложението (3.0.0)
14) Стартирайте 4.0.0
15) Изчакайте приложението (4.0.0) да се стартира
16) Генерирайте човек, като извикате POST localhost:9993/person за версия 3.0.0
17) Генерирайте човек, като извикате POST localhost:9994/person за версия 4.0.0

Стартиране на приложението в версия 1.0.0
Генериране на човек в версия 1.0.0
Изпраща пост на 127.0.0.1:9991/person. Това е отговорът:

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

Стартиране на приложението в версия 2.0.0

Генериране на човек в версия 1.0.0
Изпраща пост на 127.0.0.1:9991/person. Това е отговорът:

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

Генериране на човек в версия 2.0.0
Изпраща пост на 127.0.0.1:9992/person. Това е отговорът:

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

Убиване на приложението 1.0.0

Стартиране на приложението в версия 3.0.0

Генериране на човек в версия 2.0.0
Изпраща пост на 127.0.0.1:9992/person. Това е отговорът:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Генериране на човек в версия 3.0.0
Изпраща пост на 127.0.0.1:9993/person. Това е отговорът:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Убиване на приложението 2.0.0

Стартиране на приложението в версия 4.0.0

Генериране на човек в версия 3.0.0
Изпраща пост на 127.0.0.1:9993/person. Това е отговорът:

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

Генериране на човек в версия 4.0.0
Изпраща пост на 127.0.0.1:9994/person. Това е отговорът:

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

Промени в БД

Относно v3 просто премахваме колоната last_name и добавяме липсващите ограничения.

-- ПРЕМАХНЕТЕ КОЛОНАТА
ALTER TABLE PERSON DROP last_name;

-- ДОДАВАНЕ НА ОГРАНИЧЕНИЯ
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Промени в кода

Няма промени в кода.

Извод

Успешно възстановихме несъвместимата промяна на името на колоната, извършвайки няколко обратно съвместими разгръщания. По-долу е резюме на извършените действия:

  1. разгръщане на приложението версия 1.0.0 с v1 схема БД (име на колоната = last_name)
  2. разгръщане на приложението версия 2.0.0, което съхранява данни в last_name и surname. Приложението чете от last_name. БД е в версия v2, съдържаща колони като last_name, така и фамилия. фамилия е копие на last_name. (ЗАБЕЛЕЖКА: тази колона не трябва да има ограничение not null)
  3. разгръщане на приложението версия 3.0.0, което съхранява данни само в surname и чете от фамилия. Що се отнася до БД, последната миграция е в ход last_name в surname. Също така, ограничението NOT NULL се премахва от last_name. БД в момента е в версия v3
  4. разгръщане на приложението версия 4.0.0 — в кода не се извършват никакви промени. Деплой на базата данни v4, която изтрива last_name. Тук можете да добавите всякакви липсващи ограничения в БД.

Следвайки този подход, винаги можете да се върнете на една версия назад, без да нарушавате съвместимостта на базата данни / приложението.

Код

Целият код, използван в тази статия, е наличен на Github. По-долу е допълнително описание.

Проекти

След клониране на репозитория, ще видите следната структура на папки.

├── boot-flyway-v1              - 1.0.0 версия на приложението с v1 на схемата
├── boot-flyway-v2              - 2.0.0 версия на приложението с v2 на схемата (обратно-съвместима - приложението може да бъде върнато назад)
├── boot-flyway-v2-bad          - 2.0.0.BAD версия на приложението с v2bad на схемата (обратно-несъвместима - приложението не може да бъде върнато назад)
├── boot-flyway-v3              - 3.0.0 версия на приложението с v3 на схемата (приложението може да бъде върнато назад)
└── boot-flyway-v4              - 4.0.0 версия на приложението с v4 на схемата (приложението може да бъде върнато назад)

Скриптове

Можете да стартирате сценарии, описани в скриптовете по-долу, които ще демонстрират обратно-съвместими и несъвместими промени в БД.

За да видите случай с обратно-съвместими промени, стартирайте:

.\/scripts\/scenario_backward_compatible.sh

А за да видите случай с обратно-несъвместими промени, стартирайте:

.\/scripts\/scenario_backward_incompatible.sh

Spring Boot Sample Flyway

Всички примери са взети от Spring Boot Sample Flyway.

Можете да погледнете на http://localhost:8080/flyway, там е списъка със скриптове.

Този пример също включва конзолата H2 (на адрес http://localhost:8080/h2-console), за да можете да преглеждате състоянието на базата данни (URL jdbc по подразбиране — jdbc:h2:mem:testdb).

Допълнително

Също така прочетете други статии в нашия блог:

Източник: habr.com

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster