
В тази статия подробно се обяснява как да се решават проблемите, свързани с съвместимостта на базите данни при деплой. Ще обсъдим какво може да се случи с вашите приложения в продукция, ако опитате да извършите деплой без предварителна подготовка. След това ще преминем през етапите на жизнения цикъл на приложението, които са необходими, за да имате нулево време на престой (бележка: по-нататък — нулево време на престой). Резултатът от нашите операции ще бъде прилагането на обратно несъвместимо изменение на базата данни по обратно съвместим начин.
Ако искате да се запознаете с примери за код от статията, ще ги намерите на .
Въведение
Нулево време на престой при деплой
Какво е това мистично нулево време на престой при деплой? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.
Как да го постигнем? Има няколко начина, ето един от тях:
- разгрънете версия №1 на вашата услуга
- извършете миграция на БД
- разгрънете версия №2 на вашата услуга паралелно с версия №1
- веднага щом видите, че версия №2 работи както трябва, премахнете версия №1
- Готово!
Лесно, нали? За съжаление, не е толкова просто и по-късно ще разгледаме това по-подробно. А сега нека проверим още един доста разпространен процес на деплой — blue green deployment.
Чували ли сте някога за ? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на , където описваме това по-подробно. Накратко, нека припомним как да правите blue green deployment:
- осигурете работа на две копия на вашия production код (“синя” и “зелена”);
- насочете целия трафик към синята среда, т.е. да URL адресите на продукцията сочат натам;
- разгръщайте и тествайте всички промени на приложението в зелената среда;
- превключете URL адресите от синята на зелената среда
Blue green deployment е подход, който ви позволява лесно да въвеждате нови функции, без да се притеснявате, че продукцията ще се провали. Това се дължи на факта, че дори ако нещо се случи, можете лесно да се върнете към предишната среда, просто „щраквайки превключвателя“.
След като прочетете всичко гореизброено, може да зададете въпроса: Какво общо има нулевото време на престой с Blue green деплоя?
Какво ж, те имат доста общо, тъй като поддръжката на две копия на една и съща среда изисква двойни усилия за тяхното обслужване. Ето защо някои екипи, както твърди , се придържат към вариация на този подход:
Алтернативният вариант е да се използва същата база данни, създавайки синьо-зелени превключватели за уеб и домейновите слоеве. В такъв подход базите данни често могат да бъдат проблем, особено когато трябва да промените схемата й, за да поддържате нова версия на софтуера.
Тук стигаме до основния проблем в тази статия. База данни.. Нека отново погледнем на тази фраза.
извършете миграция на БД.
Сега трябва да си зададете въпроса – какво, ако промяната в базата данни е обратно несъвместима? Няма ли да счупи моята първа версия на приложението? Всъщност, именно това ще се случи...
По този начин, дори въпреки огромните предимства на zero downtime / blue green deployment, компаниите са склонни да следват следния, по-безопасен процес на разгръщане на своите приложения:
- подгответе пакет с новата версия на приложението
- изключете стартираното приложение
- стартирайте скриптове за миграция на базата данни
- разгръщайте и стартирайте новата версия на приложението
В тази статия подробно ще опишем как можете да работите с базата данни и кода, за да се възползвате от предимствата на zero downtime deployment.
Проблеми с базата данни
Ако имате stateless приложение, което не съхранява данни в БД, можете да постигнете zero downtime deployment веднага. За съжаление, голяма част от софтуера трябва да съхранява данни някъде. Затова трябва да помислите два пъти, преди да направите каквито и да било промени в схемата. Преди да се задълбочим в детайлите как да промените схемата по такъв начин, че да бъде възможен деплой без простои, нека първо се фокусираме върху схемата за управление на версиите.
Схема за управление на версиите
В тази статия ще използваме като инструмент за управление на версиите (бел. ред.: става дума за миграции на БД). Естествено, ще напишем и приложение 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) като пример.
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');Всичко прекрасно говори самò за себе си: можете да използвате SQL, за да определите как трябва да бъде променена вашата база данни. За повече информация относно Spring Boot и Flyway, моля, вижте .
Използвайки инструмент за управление на версиите с Spring Boot, получавате 2 големи предимства:
- отделяте промените в базата данни от промените в кода
- миграцията на базата данни се извършва заедно с разгръщането на вашето приложение, т.е. вашият процес на разгръщане се опростява
Решаване на проблеми с базата данни
В следващата част на статията ще се фокусираме върху разглеждането на два подхода към промените в базата данни.
- обратна несъвместимост
- обратна съвместимост
Първият ще бъде разгледан като предупреждение, че не бива да извършвате zero downtime deployment без предварителна подготовка… Вторият предлага решение, как можете да извършите разгръщане без просто, като същевременно поддържате обратната съвместимост.
Нашият проект, по който ще работим, ще бъде просто приложение Spring Boot Flyway, в което има Person с first_name и last_name в базата данни (прим. пер.: Person е таблица, а first_name и last_name — това са полета в нея). Искаме да преименуваме last_name в surname.
Допускания
Преди да навлезем в детайли, е необходимо да обозначим няколко допускания относно нашите приложения. Основният резултат, който искаме да постигнем, ще бъде доста прост процес.
Забележка. Business PRO-TIP. Оптимизацията на процесите може да ви спести много пари за поддръжка (колкото повече хора работят във вашата компания, толкова повече пари можете да спестите)!
Не трябва да извършвате откат на базата данни
Това опростява процеса на разгръщане (някои откати на базата данни са практически невъзможни, например откат на изтриване). Предпочитаме да откатим само приложенията. По този начин, дори ако имате различни бази данни (например SQL и NoSQL), вашият deployment pipeline ще изглежда идентично.
Трябва да има ВИНАГИ възможност за откат на приложението с една версия назад (не повече)
Откатът трябва да се прави само при необходимост. Ако в текущата версия има грешка, която е трудна за отстраняване, ние трябва да можем да се върнем към последната работеща версия. Предполагаме, че тази последна работеща версия е предишната. Поддръжката на съвместимост на кода и базата данни за повече от едно издание би било изключително трудно и скъпо.
Бележка. За по-голяма четимост, в рамките на настоящата статия, ще променяме мажорната версия на приложението.
Стъпка 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.
Стъпки:
- развернат нов екземпляр на приложението версия
2.0.0.BAD, който обновява базата данни доv2bad - в базата данни
v2badколонаlast_nameвече не съществува — променена е наsurname - обновяването на базата данни и приложението мина успешно, и някои екземпляри работят в
1.0.0, други — в2.0.0.BAD. Всички са свързани с БДv2bad - всички екземпляри на версия
1.0.0ще започнат да дават грешки, защото ще се опитат да вставят данни в колонатаlast_name, която вече я няма - всички екземпляри на версия
2.0.0.BADще работят без проблеми
Както виждате, ако правим несъвместими промени в БД и приложението, A/B тестированието е невъзможно.
Откат на приложението
Нека предположим, че след опит за извършване на A/B разполагане (прим. пер.: вероятно, тук авторът е имал предвид A/B тестиране) решихме, че ни трябва да върнем приложението до версия 1.0.0. Да предположим, че не искаме да правим откат на базата данни.
Стъпки:
- спираме инстанция на приложението версия
2.0.0.BAD - база данни все още
v2bad - тъй като версията
1.0.0не разбира какво еsurname, ще видим грешки - адът се е освободил, вече не можем да се върнем
Както виждате, ако правим обратно несъвместими промени в БД и приложението, не можем да се върнем към предишна версия.
Логове на изпълнението на скрипта
Несъвместим сценарий:
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
Изпращане на POST до 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
Изпращане на POST до 127.0.0.1:9991/person. Това е отговорът:
curl: (22) Заявеният URL върна грешка: 500 Вътрешна сървърна грешка
Генериране на човек в версия 2.0.0.BAD
Изпращане на POST до 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, той няма да се повреди по време на изпълнение.
Пускаме нова версия
Стъпки:
- извършете миграция на БД, за да създадете нова колона
surname. Сега вашата БД версияv2 - копирайте данните от
last_nameвsurname. Обърнете внимание, което, ако имате много от тези данни, трябва да обмислите пакетна миграция! - напишете код, където се използват И ДВАТА и нов, и старият колона. Сега вашето приложение е версия
2.0.0 - прочетете стойността от колоната
surname, ако не еnull, или от last_name, акоsurnameне е зададена. Можете да премахнетеgetLastName()от кода, тъй като той ще връщаnullпри възстановяване на приложението ви с3.0.0до2.0.0.
Ако използвате Spring Boot Flyway, тези две стъпки ще бъдат изпълнени по време на стартиране на версията 2.0.0 на приложението. Ако стартирате инструмента за управление на версиите на базата данни ръчно, ще трябва да направите две отделни действия (първо актуализирайте версията на базата данни ръчно, а след това разположете новото приложение).
Важно. Помнете, че новосъздадената колона НЕ ТРЯБВА този процес. NOT NULL. Ако правите възстановяване, старото приложение не знае за новата колона и няма да я установи по време на
Insert.Но ако добавите това ограничение, и вашата БД еv2, това ще изисква задаване на стойност на новата колона. Което ще доведе до нарушения на ограниченията.Важно. Трябва да премахнете метода
getLastName(), тъй като в версия3.0.0в кода не съществува концепция за колонаlast_name. Това означава, че там ще бъдат зададени null. Можете да оставите метода и да добавите проверки заnull, но много по-доброто решение ще бъде да се уверите, че в логикатаgetSurname()сте избрали правилната ненулева стойност.
A/B-тестиране
Настоящата ситуация е такава, че имаме приложение версия 1.0.0, разположено в продукция, и БД в v1. Трябва да разположим втори екземпляр на версията на приложението 2.0.0, който ще актуализира базата данни до v2.
Стъпки:
- развернат нов екземпляр на приложението версия
2.0.0, който обновява базата данни доv2 - през това време някои заявки са били обработвани от екземплярите на версията
1.0.0 - актуализацията е преминала успешно, и имате няколко работещи екземпляра на приложението на версия
1.0.0и останалите версии2.0.0.Всички комуникират с БД вv2 - версия
1.0.0не използва в БД колоната surname, а версията2.0.0използва. Те не пречат един на друг, и не би трябвало да има грешки. - версия
2.0.0съхранява данни както в старата, така и в новата колона, което осигурява обратно съвместимост
Важно. Ако имате някакви заявки, които преброяват елементи на базата на стойности от старата / нова колона, трябва да помните, че сега имате дублиране на стойности (най-вероятно те все още мигрират). Например, ако искате да преброите броя на потребителите, чиято фамилия (каквото и да е име на колоната) започва с буква
A, тогава до завършване на миграцията на данните (old→новколона) можете да имате неконсистентни данни, ако направите заявка към новата колона.
Откат на приложението
В момента имаме версия на приложението 2.0.0 и база данни в v2.
Стъпки:
- върнете приложението си до версия
1.0.0. - версия
1.0.0не използва в БД колонатаsurname, следователно връщането трябва да е успешно
Промени в 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. На този етап трябва да се направят промени в кода на приложението, насочени към премахване на функционалността, която използва колоната last_name.
Добавяйки нова колона и копирайки съдържанието й, създадохме обратно съвместими промени в БД. Също така, ако върнем JAR или имаме работещ стар JAR, той няма да се счупи по време на изпълнението.
Откат на приложението
В момента имаме приложение версия 3.0.0 и база данни v3. Версия 3.0.0 не съхранява данни в last_name. Това означава, че в surname се съхранява най-актуалната информация.
Стъпки:
- върнете приложението си до версия
2.0.0. - версия
2.0.0използва иlast_nameиsurname. - версия
2.0.0ще вземеsurname, ако не е нулево, а в противен случай —last_name
Промени в БД
В БД няма структурни промени. Изпълнява се следният скрипт, който извършва окончателната миграция на старите данни:
-- ПРИЕМАМЕ, ЧЕ ТОВА Е БЪРЗА МИГАЦИЯ - В ПРОТИВЕН СЛУЧАЙ ЩЕ ТРЯБВА ДА МИГРИРАМЕ НА ДЕЙСТВИЯ
-- СЪЩО ТАКА НЕ ПРОВЯРЯВАМЕ ДАЛИ НЕ ЗАБЛУДЯВАМЕ СЪЩЕСТВУВАЩИ ВХОДОВЕ. ТРЯБВА ДА СРАВНЯВАМЕ
-- ВЕРСИИ НА ВХОДА, ЗА ДА УБЕДИМ, ЧЕ АКО ВЕЧЕ ИМА ВХОД С ПО-ВИСОКО НОМЕР НАЗНАЧЕНИЕ
-- НЯМА ДА ГО ЗАБЛУДИМ.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;
-- ПРЕМАХВАНЕ НА ОГРАНИЧЕНИЕТО NOT NULL; В ПРОТИВЕН СЛУЧАЙ ЩЕ СЕ ОПИТАТЕ ДА ВМЕНЯТЕ 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
Изпращане на POST до 127.0.0.1:9991/person. Това е отговорът:
{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}
Стартиране на приложението в версия 2.0.0
Генериране на човек в версия 1.0.0
Изпращане на POST до 127.0.0.1:9991/person. Това е отговорът:
{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}
Генериране на човек в версия 2.0.0
Изпращане на POST до 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
Изпращане на POST до 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
Изпращане на POST до 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
Изпращане на POST до 127.0.0.1:9993/person. Това е отговорът:
{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}
Генериране на човек в версия 4.0.0
Изпращане на POST до 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.0.0сv1схеми на БД (име на колоната =last_name) - разгръщане на приложението версия
2.0.0,което съхранява данните вlast_nameиsurname. Приложението чете отlast_name. БД е в версияv2, съдържаща колони катоlast_name, така ифамилия. фамилияе копие на last_name. (ЗАБЕЛЕЖКА: тази колона не трябва да има ограничение not null) - разгръщане на приложението версия
3.0.0, което запазва данни само вsurnameи чете от фамилия. Що се отнася до БД, последната миграция се извършваlast_nameвsurname. Също така, ограничението NOT NULL се сваля отlast_name. БД сега е в версияv3 - разгръщане на приложението версия
4.0.0— в кода не се извършват никакви промени. Деплой на базата данниv4, която премахваlast_name. Тук можете да добавите всякакви липсващи ограничения в БД.
Следвайки този подход, винаги можете да се върнете на една версия назад, без да нарушавате съвместимостта на базата данни / приложението.
Код
Целият код, използван в тази статия, е достъпен на . По-долу е допълнително описание.
Проекти
След клониране на репозитория, ще видите следната структура на папките.
├── 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.shSpring 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
