Implantação de Zero Downtime e bancos de dados

Implantação de Zero Downtime e bancos de dados

Neste artigo, vamos explicar detalhadamente como resolver problemas relacionados à compatibilidade de bancos de dados durante o deploy. Falaremos sobre o que pode acontecer com suas aplicações em produção se você tentar fazer um deploy sem o devido preparo. Depois, iremos revisar as etapas do ciclo de vida da aplicação necessárias para ter tempo de inatividade zero (nota do tradutor: a seguir — zero downtime). O resultado de nossas operações será aplicar a alteração de banco de dados incompatível de uma forma compatível.

Se você deseja entender os exemplos de código do artigo, poderá encontrá-los em GitHub.

Introdução

Zero downtime deployment

Qual é o mistério por trás do zero downtime deployment? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.

Como alcançar isso? Existem várias maneiras, aqui está uma delas:

  • implante a versão nº 1 do seu serviço
  • realize a migração do banco de dados
  • implante a versão nº 2 do seu serviço paralelamente à versão nº 1
  • assim que você ver que a versão nº 2 está funcionando como deveria, remova a versão nº 1
  • pronto!

Fácil, não é? Infelizmente, não é tão simples, e discutiremos isso em detalhes mais tarde. Agora, vamos analisar outro processo de deploy bastante comum — blue green deployment.

Você já ouviu falar de blue green deployment? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на este artigo, onde descrevemos isso com mais detalhes. Em resumo, vamos relembrar como fazer blue green deployment:

  • garantir o funcionamento de duas cópias do seu código de produção (“blue” e “green”);
  • redirecionar todo o tráfego para o ambiente blue, ou seja, para que os endereços URL da produção apontem para lá;
  • implantar e testar todas as mudanças da aplicação no ambiente green;
  • alternar os endereços URL do ambiente blue para o green

O blue green deployment é uma abordagem que permite que você introduza novas funcionalidades facilmente, sem se preocupar que a produção falhe. Isso se deve ao fato de que, mesmo que algo aconteça, você pode facilmente reverter para o ambiente anterior, simplesmente "mudando o interruptor."

Após ler tudo isso, você pode se perguntar: qual a relação do zero downtime com o blue green deployment?

Bem, eles têm bastante em comum, já que manter duas cópias do mesmo ambiente requer um esforço extra para mantê-las. É por isso que algumas equipes, como afirma Martin Fowler, seguem uma variação dessa abordagem:

Outra opção é usar o mesmo banco de dados, criando switches blue-green para as camadas web e de domínio. Neste tipo de abordagem, os bancos de dados podem frequentemente se tornar um problema, especialmente quando você precisa alterar seu esquema para suportar uma nova versão do software.

E aqui chegamos ao principal problema deste artigo. Banco de dados. Vamos dar mais uma olhada nesta frase.

realize a migração do banco de dados.

Agora você deve se perguntar — e se a mudança no banco de dados for incompatível para trás? Minha primeira versão do aplicativo não irá quebrar? Na verdade, é exatamente isso que acontecerá…

Assim, mesmo com as enormes vantagens do zero downtime / blue green deployment, as empresas tendem a seguir o seguinte processo de implantação mais seguro para seus aplicativos:

  • preparar um pacote com a nova versão do aplicativo
  • desligar o aplicativo em execução
  • executar scripts para migração do banco de dados
  • desdobrar e iniciar a nova versão do aplicativo

Neste artigo, descreveremos em detalhes como você pode trabalhar com o banco de dados e o código para aproveitar as vantagens do zero downtime deployment.

Problemas com o banco de dados

Se você tiver um aplicativo stateless que não armazena dados no banco de dados, pode conseguir um zero downtime deployment imediatamente. Infelizmente, a maior parte do software precisa armazenar dados em algum lugar. É por isso que você deve pensar duas vezes antes de fazer qualquer alteração no esquema. Antes de mergulharmos nos detalhes de como alterar o esquema de tal forma que o deployment sem downtime seja possível, vamos primeiro focar no esquema de controle de versões.

Esquema de controle de versões

Neste artigo, usaremos Flyway como ferramenta para controle de versões (Nota do tradutor: estamos falando sobre migrações de banco de dados). Naturalmente, também escreveremos um aplicativo Spring Boot que tenha suporte embutido ao Flyway e realizará a migração do esquema durante a configuração do contexto do aplicativo. Ao usar o Flyway, você pode armazenar os scripts de migração na pasta dos seus projetos (por padrão em classpath:db/migration). Aqui você pode ver um exemplo desses arquivos de migração

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

Neste exemplo, vemos 4 cenários de migração que, caso não tenham sido realizados anteriormente, serão executados um após o outro ao iniciar o aplicativo. Vamos analisar um dos arquivos (V1__init.sql) como exemplo.

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

Tudo fala por si: você pode usar SQL para definir como seu banco de dados deve ser alterado. Para mais informações sobre Spring Boot e Flyway, veja Spring Boot Docs.

Usando a ferramenta de controle de versão com Spring Boot, você obtém 2 grandes vantagens:

  • você separa as mudanças do banco de dados das mudanças de código
  • a migração do banco de dados ocorre junto com o lançamento do seu aplicativo, ou seja, seu processo de implantação é simplificado

Resolução de problemas com o banco de dados

Na próxima seção do artigo, iremos nos concentrar na análise de dois abordagens para as alterações do banco de dados.

  • incompatibilidade reversa
  • compatibilidade reversa

A primeira será abordada como um aviso de que não se deve realizar uma implantação com zero downtime sem preparação prévia... A segunda oferece uma solução sobre como realizar a implantação sem interrupções e ao mesmo tempo manter a compatibilidade reversa.

Nosso projeto, que iremos desenvolver, será um aplicativo simples Spring Boot com Flyway, que possui Person com first_name e last_name no banco de dados (nota do tradutor: Person é uma tabela, e first_name e last_name — são os campos nela). Queremos renomear last_name em surname.

Suposições

Antes de nos aprofundarmos nos detalhes, é necessário estabelecer algumas suposições sobre nossos aplicativos. O principal resultado que queremos alcançar será um processo bastante simples.

Nota. DICA PRO. Simplificar processos pode economizar muito dinheiro em suporte (quanto mais pessoas trabalham na sua empresa, mais dinheiro você pode economizar)!

Não devemos fazer rollback do banco de dados

Isso simplifica o processo de implantação (alguns rollbacks de banco de dados são praticamente impossíveis, como o rollback de exclusões). Preferimos reverter apenas os aplicativos. Dessa forma, mesmo que você tenha diferentes bancos de dados (como SQL e NoSQL), seu pipeline de implantação parecerá o mesmo.

Deve haver sempre a possibilidade de reverter o aplicativo para uma versão anterior (não mais do que isso)

O rollback deve ser realizado apenas quando necessário. Se houver um erro na versão atual que é difícil de corrigir, devemos ser capazes de retornar à última versão funcional. Suponhamos que essa última versão funcional seja a anterior. Manter a compatibilidade de código e banco de dados para mais de uma versão seria extremamente difícil e caro.

NotaPara maior legibilidade, neste artigo, mudaremos a versão principal do aplicativo.

Passo 1: Estado inicial

Versão do aplicativo: 1.0.0
Versão do BD: v1

Comentário

Este será o estado inicial do aplicativo.

Alterações no BD

O BD contém 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');

Alterações no código

O aplicativo armazena dados da Person em 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
                + "]";
    }
}

Renomeação de coluna não compatível

Vamos considerar um exemplo de como mudar o nome de uma coluna:

Atenção. O próximo exemplo causará intencionalmente uma falha. Estamos mostrando isso para demonstrar o problema de compatibilidade do banco de dados.

Versão do aplicativo: 2.0.0.BAD

Versão do BD: v2bad

Comentário

As alterações atuais NÃO nos permitem executar duas instâncias (antiga e nova) simultaneamente. Assim, o zero downtime deployment será difícil de alcançar (considerando as suposições, é de fato impossível).

Teste A/B

A situação atual é que temos um aplicativo versão 1.0.0, implantado em produção, e o BD v1. Devemos implantar uma segunda instância do aplicativo, versão 2.0.0.BAD, e atualizar o banco de dados para v2bad.

Passos:

  1. foi implantada uma nova instância do aplicativo, versão 2.0.0.BAD, que atualiza o banco de dados para v2bad
  2. no banco de dados v2bad coluna last_name não existe mais — foi alterada para surname
  3. a atualização do banco de dados e do aplicativo foi bem-sucedida, e algumas instâncias estão funcionando em 1.0.0, outras em 2.0.0.BAD. Todos estão conectados ao BD v2bad
  4. todas as instâncias da versão 1.0.0 começarão a gerar erros, pois tentarão inserir dados na coluna last_name, que não existe mais
  5. todas as instâncias da versão 2.0.0.BAD funcionarão sem problemas

Como você pode ver, se fizermos alterações em BD e aplicativo que não são compatíveis, o teste A/B é impossível.

Rollback do aplicativo

Vamos supor que, após tentar realizar a implantação A/B (nota do tradutor: provavelmente o autor se referia ao teste A/B) decidimos que precisamos reverter o aplicativo para a versão 1.0.0. Suponha que não queremos realizar o rollback do banco de dados.

Passos:

  1. paramos o aplicativo na versão 2.0.0.BAD
  2. o banco de dados ainda está v2bad
  3. porque a versão 1.0.0 não entende o que é surname, veremos erros
  4. o inferno foi solto, não podemos mais voltar

Como você pode ver, se fizermos alterações incompatíveis com versões anteriores no banco de dados e no aplicativo, não podemos reverter para a versão anterior.

Logs de execução do script

Cenário incompatível com versões anteriores:

01) Execute 1.0.0
02) Aguarde o aplicativo (1.0.0) iniciar
03) Gere uma pessoa chamando POST localhost:9991/person para a versão 1.0.0
04) Execute 2.0.0.BAD
05) Aguarde o aplicativo (2.0.0.BAD) iniciar
06) Gere uma pessoa chamando POST localhost:9991/person para a versão 1.0.0 <--- isso deve falhar
07) Gere uma pessoa chamando POST localhost:9992/person para a versão 2.0.0.BAD <--- isso deve passar

Iniciando o aplicativo na versão 1.0.0
Gere uma pessoa na versão 1.0.0
Enviando um POST para 127.0.0.1:9991/person. Esta é a resposta:

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

Iniciando o aplicativo na versão 2.0.0.BAD
Gere uma pessoa na versão 1.0.0
Enviando um POST para 127.0.0.1:9991/person. Esta é a resposta:

curl: (22) A URL solicitada retornou erro: 500 Internal Server Error

Gere uma pessoa na versão 2.0.0.BAD
Enviando um POST para 127.0.0.1:9995/person. Esta é a resposta:

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

Alterações no BD

Script de migração que renomeia last_name em surname

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

Script que renomeia last_name.

-- Esta alteração é incompatível com versões anteriores - não é possível realizar testes A/B
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Alterações no código

Mudamos o nome do campo lastName para surname.

Renomeando a coluna de maneira compatível

Esta é a situação mais comum que podemos enfrentar. Precisamos fazer alterações incompatíveis com versões anteriores. Já provamos que para implementações sem interrupções, não devemos apenas aplicar a migração do banco de dados sem ações adicionais. Nesta seção do artigo, realizaremos 3 implementações do aplicativo junto com as migrações do banco de dados para alcançar o resultado desejado e, ao mesmo tempo, manter a compatibilidade reversa.

Nota. Lembre-se de que temos um banco de dados na versão v1. Ele contém colunas first_name e last_name. Precisamos mudar last_name para surname. Também temos um aplicativo na versão 1.0.0, que ainda não utiliza surname.

Passo 2: Adicionando surname

Versão do aplicativo: 2.0.0
Versão do BD: v2

Comentário

Ao adicionar uma nova coluna e copiar seu conteúdo, criamos alterações compatíveis com versões anteriores no banco de dados. Ao mesmo tempo, se revertermos o JAR ou tivermos um JAR antigo funcionando, ele não falhará durante a execução.

Implantando uma nova versão

Passos:

  1. realize a migração do banco de dados para criar uma nova coluna surname. Agora seu banco de dados na versão v2
  2. copie os dados de last_name em surname. Note que, e se você tiver muitos desses dados, deve considerar uma migração em lote!
  3. escreva o código onde são utilizados AMBOS e novo, quanto antigo coluna. Agora seu aplicativo está na versão 2.0.0
  4. leia o valor da coluna surname, se não null, ou da last_name, se surname não estiver definido. Você pode remover getLastName() do código, pois ele gerará null ao reverter seu aplicativo de 3.0.0 até 2.0.0.

Se você estiver usando o Spring Boot Flyway, essas duas etapas serão executadas durante o início da versão 2.0.0 do aplicativo. Se você estiver executando a ferramenta de gerenciamento de versões do banco de dados manualmente, terá que fazer duas ações diferentes para isso (primeiro, atualize a versão do banco manualmente e depois implante o novo aplicativo).

É importante. Lembre-se de que a nova coluna NÃO DEVE ser NOT NULL. Se você fizer o reverter, o aplicativo antigo não saberá sobre a nova coluna e não a definirá durante Insert. Mas se você adicionar essa restrição, e seu BD for v2, isso exigirá a definição de um valor para a nova coluna. O que levará à violação das restrições.

É importante. Você deve remover o método getLastName(), pois na versão 3.0.0 o conceito de coluna não existe no código last_name. Isso significa que serão definidos null. Você pode deixar o método e adicionar verificações em null, mas a solução muito melhor seria garantir que na lógica getSurname() você escolheu o valor não nulo correto.

Teste A/B

A situação atual é que temos um aplicativo versão 1.0.0, implantado em produção, e o BD em v1. Devemos implantar a segunda instância do aplicativo da versão 2.0.0, que atualizará o banco de dados para v2.

Passos:

  1. foi implantada uma nova instância do aplicativo, versão 2.0.0, que atualiza o banco de dados para v2
  2. enquanto isso, algumas solicitações foram processadas pelas instâncias da versão 1.0.0
  3. a atualização foi bem-sucedida e você tem várias instâncias funcionais do aplicativo da versão 1.0.0 e as outras versões 2.0.0. Todos estão se comunicando com o BD em v2
  4. versão 1.0.0 não usa a coluna sobrenome no BD, enquanto a versão 2.0.0 usa. Elas não se atrapalham e não devem haver erros.
  5. versão 2.0.0 mantém os dados tanto na coluna antiga quanto na nova, garantindo compatibilidade retroativa

É importante. Se você tiver alguma solicitação que conte elementos com base em valores da coluna antiga / nova, deve lembrar que agora você tem duplicação de valores (provavelmente, ainda estão migrando). Por exemplo, se você quiser contar o número de usuários cuja sobrenome (seja qual for o nome da coluna) começa com a letra A, então até a conclusão da migração de dados (old → novo coluna) você pode ter dados inconsistentes se executar uma solicitação à nova coluna.

Rollback do aplicativo

Agora temos o aplicativo da versão 2.0.0 e o banco de dados em v2.

Passos:

  1. reverter seu aplicativo para a versão 1.0.0.
  2. versão 1.0.0 não usa a coluna no BD surname, portanto o rollback deve ser bem-sucedido

Alterações no DB

O DB contém uma coluna chamada last_name.

Script original do 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 adição surname.

Atenção. Lembre-se de que NÃO É PERMITIDO ADICIONAR quaisquer restrições NOT NULL na coluna adicionada. Se você fizer rollback do JAR, a versão antiga não terá conhecimento da coluna adicionada e automaticamente definirá seu valor como NULL. Se houver tal restrição, o aplicativo antigo simplesmente falhará.

-- OBSERVAÇÃO: Este campo não pode ter a restrição NOT NULL, pois se você reverter, a versão antiga não saberá sobre este campo
-- e sempre definirá como NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- ESTAMOS ASSUMINDO QUE É UMA MIGRAÇÃO RÁPIDA - CASO CONTRÁRIO, TERÍAMOS QUE MIGRAR EM LOTES
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Alterações no código

Estamos armazenando dados tanto em last_name, quanto em surname. Enquanto isso, lemos de last_name, pois essa coluna é a mais atual. Durante o processo de implantação, algumas consultas podem ter sido processadas por uma instância do aplicativo que ainda não foi atualizada.

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

Etapa 3: Removendo last_name do código

Versão do aplicativo: 3.0.0

Versão do BD:v3

Comentário

Nota do tradutor: Aparentemente, no artigo original, o autor copiou erroneamente o texto deste bloco da etapa 2. Nesta etapa, devem ser feitas alterações no código do aplicativo visando remover a funcionalidade que utiliza a coluna last_name.

Ao adicionar uma nova coluna e copiar seu conteúdo, criamos alterações compatíveis com versões anteriores no DB. Além disso, se fizermos rollback do JAR ou tivermos um JAR antigo em funcionamento, ele não falhará durante a execução.

Rollback do aplicativo

Atualmente temos um aplicativo na versão 3.0.0 e o banco de dados v3. A versão 3.0.0 não armazena dados em last_name. Isso significa que em surname está armazenada a informação mais recente.

Passos:

  1. reverter seu aplicativo para a versão 2.0.0.
  2. versão 2.0.0 usa e last_name e surname.
  3. versão 2.0.0 pegará surname, se não for nulo, caso contrário, —last_name

Alterações no BD

Não há alterações estruturais no DB. O seguinte script é executado, que realiza a migração final dos dados antigos:

-- ESTAMOS ASSUMINDO QUE É UMA MIGRAÇÃO RÁPIDA - CASO CONTRÁRIO, TERÍAMOS QUE MIGRAR EM LOTES
-- TAMBÉM NÃO ESTAMOS VERIFICANDO SE NÃO ESTAMOS SOBREECRITANDO ENTRADAS EXISTENTES. TERÍAMOS QUE COMPARAR
-- VERSÕES DE ENTRADA PARA GARANTIR QUE, SE HÁ UMA ENTRADA COM UM NÚMERO DE VERSÃO MAIOR,
-- NÃO A SOBREECRITAREMOS.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- REMOVENDO A RESTRIÇÃO NOT NULL; CASO CONTRÁRIO, VOCÊ TENTARÁ INSERIR UM VALOR NULO NO LAST_NAME
-- COM UMA RESTRIÇÃO NOT NULL.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Alterações no código

Nota do tradutor: A descrição deste bloco também foi copiada erroneamente pelo autor a partir da etapa 2. De acordo com a lógica da narrativa do artigo, as alterações no código nesta etapa devem visar remover os elementos responsáveis pela manipulação da coluna last_name.

Estamos armazenando dados tanto em last_name, quanto em surname. Além disso, lemos da coluna last_name, pois é a mais relevante. Durante o processo de implantação, algumas solicitações podem ser processadas por uma instância que ainda não foi atualizada.

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

Passo 4: Removendo last_name do BD

Versão do aplicativo: 4.0.0

Versão do BD: v4

Comentário

Devido ao fato de que o código da versão 3.0.0 não usou a coluna last_name, durante a execução, nada de grave ocorrerá se retrocedermos para 3.0.0 após a remoção da coluna do banco de dados.

Logs de execução do script

Faremos da seguinte forma:

01) Execute 1.0.0
02) Aguarde o aplicativo (1.0.0) iniciar
03) Gere uma pessoa chamando POST localhost:9991/person para a versão 1.0.0
04) Execute 2.0.0
05) Aguarde o aplicativo (2.0.0) iniciar
06) Gere uma pessoa chamando POST localhost:9991/person para a versão 1.0.0
07) Gere uma pessoa chamando POST localhost:9992/person para a versão 2.0.0
08) Finalize o aplicativo (1.0.0)
09) Execute 3.0.0
10) Aguarde o aplicativo (3.0.0) iniciar
11) Gere uma pessoa chamando POST localhost:9992/person para a versão 2.0.0
12) Gere uma pessoa chamando POST localhost:9993/person para a versão 3.0.0
13) Finalize o aplicativo (3.0.0)
14) Execute 4.0.0
15) Aguarde o aplicativo (4.0.0) iniciar
16) Gere uma pessoa chamando POST localhost:9993/person para a versão 3.0.0
17) Gere uma pessoa chamando POST localhost:9994/person para a versão 4.0.0

Iniciando o aplicativo na versão 1.0.0
Gerando uma pessoa na versão 1.0.0
Enviando um post para 127.0.0.1:9991/person. Esta é a resposta:

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

Iniciando o aplicativo na versão 2.0.0

Gerando uma pessoa na versão 1.0.0
Enviando um post para 127.0.0.1:9991/person. Esta é a resposta:

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

Gerando uma pessoa na versão 2.0.0
Enviando um post para 127.0.0.1:9992/person. Esta é a resposta:

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

Finalizando o aplicativo 1.0.0

Iniciando o aplicativo na versão 3.0.0

Gerando uma pessoa na versão 2.0.0
Enviando um post para 127.0.0.1:9992/person. Esta é a resposta:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Gerando uma pessoa na versão 3.0.0
Enviando um post para 127.0.0.1:9993/person. Esta é a resposta:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Finalizando o aplicativo 2.0.0

Iniciando o aplicativo na versão 4.0.0

Gerando uma pessoa na versão 3.0.0
Enviando um post para 127.0.0.1:9993/person. Esta é a resposta:

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

Gerando uma pessoa na versão 4.0.0
Enviando um post para 127.0.0.1:9994/person. Esta é a resposta:

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

Mudanças no BD

Em relação a v3 estamos apenas removendo a coluna last_name e adicionando as restrições ausentes.

-- REMOVER A COLUNA
ALTER TABLE PERSON DROP last_name;

-- ADICIONAR RESTRIÇÕES
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Alterações no código

Não há mudanças no código.

Saída

Aplicamos com sucesso a mudança de nome de coluna incompatível de volta, realizando várias implantações compatíveis com versões anteriores. Abaixo está um resumo das ações realizadas:

  1. implante do aplicativo versão 1.0.0 com v1 esquemas do BD (nome da coluna = last_name)
  2. implante do aplicativo versão 2.0.0, que armazena dados em last_name e surname. O aplicativo lê de last_name. O BD está na versão v2, contendo colunas como last_name, quanto o sobrenome. sobrenome é uma cópia de last_name. (NOTA: esta coluna não deve ter a restrição not null)
  3. implante do aplicativo versão 3.0.0, que mantém dados apenas em surname e lê de sobrenome. No que diz respeito ao BD, a última migração ocorre last_name em surname. A restrição NOT NULL é removida de last_name. O BD agora está na versão v3
  4. implante do aplicativo versão 4.0.0 — nenhuma alteração de código é feita. O deploy do banco de dados v4, que remove last_name. Aqui você pode adicionar quaisquer restrições ausentes no BD.

Seguindo essa abordagem, você sempre pode reverter uma versão sem quebrar a compatibilidade do banco de dados / aplicativo.

Código

Todo o código usado neste artigo está disponível em Github. Abaixo está uma descrição adicional.

Projetos

Após clonar o repositório, você verá a seguinte estrutura de pastas.

├── boot-flyway-v1              - versão 1.0.0 do aplicativo com a v1 do esquema
├── boot-flyway-v2              - versão 2.0.0 do aplicativo com a v2 do esquema (compatível com versões anteriores - o aplicativo pode ser revertido)
├── boot-flyway-v2-bad          - versão 2.0.0.BAD do aplicativo com a v2bad do esquema (incompatível com versões anteriores - o aplicativo não pode ser revertido)
├── boot-flyway-v3              - versão 3.0.0 do aplicativo com a v3 do esquema (o aplicativo pode ser revertido)
└── boot-flyway-v4              - versão 4.0.0 do aplicativo com a v4 do esquema (o aplicativo pode ser revertido)

Scripts

Você pode executar os scripts descritos abaixo, que demonstram mudanças compatíveis e incompatíveis com versões anteriores no BD.

Para ver o caso de mudanças compatíveis com versões anteriores, execute:

.\/scripts\/scenario_backward_compatible.sh

E para ver o caso de mudanças incompatíveis com versões anteriores, execute:

.\/scripts\/scenario_backward_incompatible.sh

Exemplo de Flyway do Spring Boot

Todos os exemplos são retirados de Exemplo de Flyway do Spring Boot.

Você pode dar uma olhada em http://localhost:8080/flyway, lá está a lista de scripts.

Este exemplo também inclui o console H2 (em http://localhost:8080/h2-console), para que você possa visualizar o estado do banco de dados (URL jdbc padrão — jdbc:h2:mem:testdb).

Além disso

Leia também outros artigos em nosso blog:

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster