Déploiement sans arrêt et bases de données

Déploiement sans arrêt et bases de données

Cet article explique en détail comment résoudre les problèmes de compatibilité des bases de données lors du déploiement. Nous discuterons des conséquences possibles pour vos applications en production si vous essayez de déployer sans préparation préalable. Ensuite, nous passerons en revue les étapes du cycle de vie d'une application nécessaires pour avoir un temps d'arrêt nul (note : ci-après - temps d'arrêt nul). Le résultat de nos opérations sera l'application d'un changement de base de données incompatible à nouveau de manière compatible.

Si vous souhaitez explorer des exemples de code de l'article, vous les trouverez sur GitHub.

Introduction

Déploiement à temps d'arrêt nul

Qu'est-ce que ce mystérieux déploiement à temps d'arrêt nul? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.

Comment y parvenir ? Il existe plusieurs méthodes, en voici une :

  • déployez la version n°1 de votre service
  • effectuez la migration de la base de données
  • déployez la version n°2 de votre service en parallèle avec la version n°1
  • dès que vous constatez que la version n°2 fonctionne comme il se doit, retirez la version n°1
  • c'est fait !

Simple, n'est-ce pas ? Malheureusement, ce n'est pas si simple, et nous allons le détailler plus tard. Mais pour l'instant, examinons un autre processus de déploiement assez courant : le déploiement blue green.

Avez-vous déjà entendu parler du déploiement blue green? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на cet article, où nous le décrivons plus en détail. Pour résumer brièvement, rappelez-vous comment effectuer un déploiement blue green :

  • assurez-vous que deux copies de votre code de production fonctionnent (“bleu” et “vert”);
  • diriger tout le trafic vers l'environnement bleu, c'est-à-dire que les URL de production pointent vers cela;
  • déployer et tester toutes les modifications de l'application dans l'environnement vert;
  • basculer les URL de l'environnement bleu à l'environnement vert

Le déploiement blue green est une approche qui vous permet d'introduire facilement de nouvelles fonctionnalités, sans craindre que la production ne tombe en panne. Cela est dû au fait que même si quelque chose se passe mal, vous pouvez facilement revenir à l'environnement précédent en un simple « interrupteur ».

Après avoir lu tout cela, vous pourriez vous demander : quelle est la relation entre le temps d'arrêt nul et le déploiement blue green ?

Eh bien, ils ont pas mal en commun, car le maintien de deux copies du même environnement nécessite des efforts doublés pour leur entretien. C'est pourquoi certaines équipes, comme le soutient Martin Fowler, adhèrent à une variation de cette approche :

Une autre option consiste à utiliser la même base de données, en créant des environnements blue-green pour les couches web et domaine. Dans cette approche, les bases de données peuvent souvent poser problème, surtout lorsque vous devez modifier leur schéma pour prendre en charge une nouvelle version du logiciel.

Et ici, nous arrivons au principal problème de cet article. Base de données. Jetons un nouveau regard sur cette phrase.

Effectuez la migration de la base de données.

Maintenant, vous devez vous poser la question : que se passe-t-il si le changement de base de données est rétro-incompatible ? Ma première version de l'application ne va-t-elle pas se casser ? En réalité, c'est précisément ce qui se passera...

Ainsi, même avec les énormes avantages du zero downtime/blue green deployment, les entreprises ont tendance à suivre le processus de déploiement plus sûr pour leurs applications :

  • préparer un paquet avec la nouvelle version de l'application
  • arrêter l'application en cours d'exécution
  • exécuter les scripts pour la migration de la base de données
  • déployer et démarrer la nouvelle version de l'application

Dans cet article, nous décrirons en détail comment vous pouvez travailler avec la base de données et le code pour tirer parti du zero downtime deployment.

Problèmes liés à la base de données

Si vous avez une application stateless qui ne stocke aucune donnée dans la base de données, vous pouvez bénéficier du zero downtime deployment immédiatement. Malheureusement, la plupart des logiciels doivent stocker des données quelque part. C'est pourquoi vous devez réfléchir à deux fois avant d'apporter des modifications au schéma. Avant d'entrer dans les détails sur la façon de modifier le schéma pour permettre un déploiement sans interruption, concentrons-nous d'abord sur le schéma de gestion des versions.

Schéma de gestion des versions

Dans cet article, nous allons utiliser Flyway comme outil de gestion des versions (note du traducteur : il s'agit de migrations de base de données). Naturellement, nous allons également écrire une application Spring Boot qui a un support intégré pour Flyway et effectuera la migration du schéma lors de la configuration du contexte de l'application. En utilisant Flyway, vous pouvez stocker les scripts de migration dans le dossier de vos projets (par défaut dans classpath:db/migration). Voici un exemple de ces fichiers de migration :

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

Dans cet exemple, nous voyons 4 scénarios de migration qui, s'ils n'ont pas été exécutés auparavant, seront exécutés l'un après l'autre lors du démarrage de l'application. Examinons l'un des fichiers (V1__init.sql) en guise d'exemple.

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

Tout parle de lui-même : vous pouvez utiliser SQL pour définir comment votre base de données doit être modifiée. Pour plus d'informations sur Spring Boot et Flyway, consultez les Docs Spring Boot.

En utilisant l’outil de gestion de version avec Spring Boot, vous obtenez 2 grands avantages :

  • vous séparez les modifications de la base de données des modifications de code
  • la migration de la base de données se fait avec le déploiement de votre application, c'est-à-dire que votre processus de déploiement est simplifié

Résolution des problèmes liés à la base de données

Dans la section suivante de l'article, nous nous concentrerons sur deux approches concernant les modifications de la base de données.

  • incompatibilité inverse
  • compatibilité inverse

La première sera abordée comme un avertissement qu'il ne faut pas effectuer de déploiement à zéro temps d'arrêt sans préparation préalable… La seconde propose une solution pour déployer sans temps d'arrêt tout en maintenant la compatibilité inverse.

Notre projet sur lequel nous allons travailler sera une simple application Spring Boot Flyway contenant Person avec first_name et last_name dans la base de données (note du traducteur : Person est une table, et first_name et last_name — ce sont des champs dans celle-ci). Nous voulons renommer last_name dans surname.

Hypothèses

Avant de plonger dans les détails, il est nécessaire d'énoncer quelques hypothèses concernant nos applications. Le principal résultat que nous voulons atteindre sera un processus assez simple.

Remarque. ASTUCE PRO BUSINESS. Simplifier les processus peut vous faire économiser beaucoup d'argent en maintenance (plus il y a de personnes dans votre entreprise, plus vous pouvez économiser d'argent) !

Il ne faut pas faire de rollback de la base de données

Cela simplifie le processus de déploiement (certaines restaurations de base de données sont pratiquement impossibles, par exemple, rollback d'une suppression). Nous préférons ne rollback que les applications. Ainsi, même si vous avez différentes bases de données (par exemple, SQL et NoSQL), votre pipeline de déploiement sera identique.

Il faut toujours avoir la possibilité de rollback l'application d'une version (pas plus)

Un rollback ne doit être effectué que si cela est nécessaire. Si la version actuelle présente une erreur difficile à corriger, nous devons être en mesure de revenir à la dernière version fonctionnelle. Nous supposons que cette dernière version fonctionnelle est la précédente. Garantir la compatibilité du code et de la base de données pour plus d'un déploiement serait extrêmement difficile et coûteux.

Note. Pour plus de lisibilité, dans cet article, nous allons modifier la version majeure de l'application.

Étape 1 : État initial

Version de l'application : 1.0.0
Version de la base de données : v1

Commentaire

Ceci sera l'état initial de l'application.

Modifications de la base de données

La base de données contient 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');

Modifications de code

L'application stocke les données Person dans 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
                + "]";
    }
}

Renommage de colonne incompatible avec les versions précédentes

Considérons un exemple de changement de nom de colonne :

Attention. L'exemple suivant provoquera intentionnellement une erreur. Nous le montrons pour illustrer le problème de compatibilité de la base de données.

Version de l'application : 2.0.0.BAD

Version de la base de données : v2bad

Commentaire

Les changements en cours ne nous permettent PAS de faire fonctionner deux instances (ancienne et nouvelle) simultanément. Par conséquent, le déploiement sans temps d'arrêt sera difficile à réaliser (sous condition, c'est en réalité impossible).

Test A/B

La situation actuelle est la suivante : nous avons une application version 1.0.0, déployée en production, et la base de données v1. Nous devons déployer une seconde instance de l'application, version 2.0.0.BAD, et mettre à jour la base de données jusqu'à v2bad.

Étapes :

  1. une nouvelle instance de l'application version 2.0.0.BAD, qui met à jour la base de données jusqu'à v2bad
  2. dans la base de données v2bad colonne last_name n'existe plus — elle a été changée en surname
  3. la mise à jour de la base de données et de l'application a été effectuée avec succès, et certaines instances fonctionnent en 1.0.0, d'autres en 2.0.0.BAD. Tous sont connectés à la base de données v2bad
  4. toutes les instances version 1.0.0 commenceront à générer des erreurs, car elles tenteront d'insérer des données dans la colonne last_name, qui n'existe plus
  5. toutes les instances version 2.0.0.BAD fonctionneront sans problème

Comme vous pouvez le voir, si nous effectuons des changements de base de données et d'application incompatibles, le test A/B est impossible.

Rollback de l'application

Supposons qu'après avoir tenté d'effectuer un déploiement A/B (note du traducteur : l'auteur fait probablement référence au test A/B), nous avons décidé qu'il nous fallait rollback de l'application à la version 1.0.0. Supposons que nous ne souhaitons pas effectuer de rollback de la base de données.

Étapes :

  1. nous arrêtons l'instance de l'application version 2.0.0.BAD
  2. la base de données est toujours v2bad
  3. puisque la version 1.0.0 ne comprend pas ce que c'est surname, nous verrons des erreurs
  4. l'enfer s'est libéré, nous ne pouvons plus revenir en arrière

Comme vous pouvez le constater, si nous effectuons des modifications non compatibles avec la base de données et l'application, nous ne pouvons pas revenir à la version précédente.

Les journaux d'exécution du script

Scénario non compatible en arrière :

01) Exécutez 1.0.0
02) Attendez que l'application (1.0.0) démarre
03) Générez une personne en appelant POST localhost:9991/person à la version 1.0.0
04) Exécutez 2.0.0.BAD
05) Attendez que l'application (2.0.0.BAD) démarre
06) Générez une personne en appelant POST localhost:9991/person à la version 1.0.0 <-- cela devrait échouer
07) Générez une personne en appelant POST localhost:9992/person à la version 2.0.0.BAD <-- cela devrait passer

Démarrage de l'application en version 1.0.0
Générez une personne en version 1.0.0
Envoi d'un post à 127.0.0.1:9991/person. Voici la réponse :

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

Démarrage de l'application en version 2.0.0.BAD
Générez une personne en version 1.0.0
Envoi d'un post à 127.0.0.1:9991/person. Voici la réponse :

curl: (22) L'URL demandée a renvoyé une erreur : 500 Erreur interne du serveur

Générez une personne en version 2.0.0.BAD
Envoi d'un post à 127.0.0.1:9995/person. Voici la réponse :

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

Modifications de la base de données

Le script de migration qui renomme last_name dans surname

Script Flyway d'origine :

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

Le script qui renomme last_name.

-- Ce changement est non compatible avec le passé - vous ne pouvez pas faire de test A/B
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Modifications de code

Nous avons changé le nom du champ lastName sur surname.

Renommer le champ de manière compatible

C'est la situation la plus courante à laquelle nous pouvons être confrontés. Nous devons effectuer des modifications non compatibles. Nous avons déjà prouvé qu'il n'est pas possible de déployer sans temps d'arrêt simplement en appliquant une migration de base de données sans actions supplémentaires. Dans cette section de l'article, nous procéderons à 3 déploiements de l'application avec des migrations de base de données, afin d'atteindre le résultat souhaité tout en maintenant la compatibilité ascendante.

Remarque. Rappelons que nous avons une base de données version v1. Elle contient des colonnes first_name et last_name. Nous devons changer last_name sur surname. Nous avons également une application version 1.0.0, qui n'utilise pas encore surname.

Étape 2 : Ajout de surname

Version de l'application : 2.0.0
Version de la base de données : v2

Commentaire

En ajoutant une nouvelle colonne et en copiant son contenu, nous créons des modifications compatibles avec la base de données. En même temps, si nous revenons à un ancien JAR ou si nous avons un ancien JAR fonctionnel, celui-ci ne se cassera pas pendant l'exécution.

Déploiement de la nouvelle version

Étapes :

  1. effectuez la migration de la base de données pour créer une nouvelle colonne surname. Maintenant votre base de données version v2
  2. copiez les données de last_name dans surname. Veuillez noter, et si vous avez beaucoup de ces données, vous devriez envisager une migration par lot !
  3. écrivez du code où sont utilisés LES DEUX et sectionet ancien colonne. Maintenant, votre application est en version 2.0.0
  4. lisez la valeur de la colonne surname, si elle n'est pas null, ou de l'last_name, si surname non défini. Vous pouvez supprimer getLastName() du code, car il retournera null lors du retour en arrière de votre application avec 3.0.0 à 2.0.0.

Si vous utilisez Spring Boot Flyway, ces deux étapes seront effectuées au démarrage de la version 2.0.0 de l'application. Si vous exécutez l'outil de gestion des versions de la base de données manuellement, vous devrez effectuer deux actions distinctes (mettre à jour manuellement la version de la base de données, puis déployer la nouvelle application).

Important. N'oubliez pas que la colonne nouvellement créée NE DOIT PAS ce processus. NOT NULL. Si vous effectuez un retour en arrière, l'ancienne application ne connaîtra pas la nouvelle colonne et ne l'installera pas lors de l'insertion. Mais si vous ajoutez cette contrainte, et que votre base de données est v2, cela nécessitera de définir la valeur de la nouvelle colonne. Ce qui entraînera des violations de contraintes.

Important. Vous devriez supprimer la méthode getLastName(), car dans la version 3.0.0 le code n'a pas de notion de colonne last_name. Cela signifie que des valeurs null y seront établies. Vous pouvez laisser la méthode et ajouter des vérifications sur null, mais une bien meilleure solution serait de vous assurer que dans la logique getSurname() vous avez choisi la bonne valeur non nulle.

Test A/B

La situation actuelle est la suivante : nous avons une application version 1.0.0, déployée en production, et la base de données en v1. Nous devons déployer la deuxième instance de l'application version 2.0.0, qui mettra à jour la base de données jusqu'à v2.

Étapes :

  1. une nouvelle instance de l'application version 2.0.0, qui met à jour la base de données jusqu'à v2
  2. pendant ce temps, certaines requêtes ont été traitées par des instances de version 1.0.0
  3. la mise à jour a réussi, et vous avez plusieurs instances fonctionnelles de l'application version 1.0.0 et d'autres versions 2.0.0. Tous communiquent avec la base de données en v2
  4. version 1.0.0 ne utilisant pas dans la base de données la colonne surname, tandis que la version 2.0.0 l'utilise. Elles ne se gênent pas mutuellement et il ne devrait pas y avoir d'erreurs.
  5. version 2.0.0 conserve les données aussi bien dans l'ancienne que dans la nouvelle colonne, ce qui assure la rétrocompatibilité

Important. Si vous avez des requêtes qui comptent des éléments sur la base de valeurs de l'ancienne / nouvelle colonne, vous devez vous rappeler qu'il y a maintenant une duplication des valeurs (il est probable qu'elles soient encore en migration). Par exemple, si vous souhaitez compter le nombre d'utilisateurs dont le nom de famille (quel que soit le nom de la colonne) commence par la lettre A, alors jusqu'à ce que la migration des données soit terminée (old → new colonne), vous pourriez avoir des données inconsistantes si vous interrogez la nouvelle colonne.

Rollback de l'application

Nous avons maintenant l'application version 2.0.0 et la base de données en v2.

Étapes :

  1. effectuez un retour en arrière de votre application à la version 1.0.0.
  2. version 1.0.0 ne utilisant pas dans la base de données la colonne surname, donc le retour doit être réussi

Modifications de la DB

La base de données contient une colonne nommée last_name.

Script Flyway d'origine :

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 d'ajout surname.

Attention. N'oubliez pas qu'il est IMPOSSIBLE D'AJOUTER des restrictions NOT NULL à la colonne ajoutée. Si vous revenez à un JAR, l'ancienne version ne connaît pas la colonne ajoutée et la définira automatiquement sur NULL. En cas de restriction, l'ancienne application échouera simplement.

-- REMARQUE : Ce champ ne peut pas avoir la contrainte NOT NULL car si vous effectuez un retour en arrière, l'ancienne version ne saura pas ce champ
-- et le définira toujours sur NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- NOUS PARTONS DU PRINCIPE QU'IL S'AGIT D'UNE MIGRATION RAPIDE - SINON NOUS DEVIONS MIGRER PAR LOTS
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Modifications de code

Nous enregistrons les données à la fois dans last_name, ainsi que dans surname. En lisant depuis last_name, car cette colonne est la plus pertinente. Au cours du déploiement, certaines requêtes ont pu être traitées par une instance de l'application qui n'était pas encore mise à jour.

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

Étape 3 : Suppression de last_name du code

Version de l'application : 3.0.0

Version de la base de données :v3

Commentaire

Note de traducteur : Il semble que dans l'article original, l'auteur ait erronément copié le texte de ce bloc depuis l'étape 2. À cette étape, des modifications du code de l'application doivent être effectuées pour supprimer la fonctionnalité qui utilise la colonne last_name.

En ajoutant une nouvelle colonne et en copiant son contenu, nous avons créé des modifications de la DB compatibles en arrière. Aussi, si nous revenons à un JAR ou si nous avons un ancien JAR fonctionnant, il ne se brisera pas lors de l'exécution.

Rollback de l'application

Nous avons actuellement l'application version 3.0.0 et la base de données v3. La version 3.0.0 ne conserve pas les données dans last_name. Cela signifie que dans surname se trouve l'information la plus récente.

Étapes :

  1. effectuez un retour en arrière de votre application à la version 2.0.0.
  2. version 2.0.0 utilise et last_name et surname.
  3. version 2.0.0 prendra surname, s'il n'est pas nul, sinon -last_name

Modifications de la base de données

Il n'y a pas de modifications structurelles dans la base de données. Le script suivant est exécuté, qui effectue la migration finale des anciennes données :

-- NOUS PARTONS DU PRINCIPE QU'IL S'AGIT D'UNE MIGRATION RAPIDE - SINON NOUS DEVIONS MIGRER PAR LOTS
-- NOUS NE VÉRIFIONS ÉGALEMENT PAS SI NOUS NE REMPLAÇONS PAS DES ENTRÉES EXISTANTES. NOUS DEVIONS COMPARER
-- LES VERSIONS D'ENTRÉE POUR S'ASSURER QUE S'IL EXISTE DÉJÀ UNE ENTRÉE AVEC UN NUMÉRO DE VERSION SUPÉRIEUR
-- NOUS NE LA REMPLACERONS PAS.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- SUPPRESSION DE LA CONTRAINTE NOT NULL ; SINON VOUS ESSAYEREZ D'INSÉRER UNE VALEUR NULL DE LAST_NAME
-- AVEC UNE CONTRAINTE NOT NULL.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Modifications de code

Note de traducteur : La description de ce bloc a également été copiée par erreur par l'auteur depuis l'étape 2. Conformément à la logique de l'article, les modifications du code à cette étape doivent viser à supprimer les éléments interagissant avec la colonne last_name.

Nous stockons les données à la fois dans last_name, ainsi que dans surname. De plus, nous lisons à partir de la colonne last_name, car elle est la plus pertinente. Lors du déploiement, certaines requêtes peuvent être traitées par une instance qui n'a pas encore été mise à jour.

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

Étape 4 : Suppression de last_name de la BDD

Version de l'application : 4.0.0

Version de la base de données : v4

Commentaire

En raison du fait que le code de version 3.0.0 n'a pas utilisé la colonne last_name, il ne se passera rien de grave pendant l'exécution si nous revenons à 3.0.0 après la suppression de la colonne de la base de données.

Les journaux d'exécution du script

Nous allons procéder de la manière suivante :

01) Exécuter 1.0.0
02) Attendre que l'application (1.0.0) démarre
03) Générer une personne en appelant POST localhost:9991/person à la version 1.0.0
04) Exécuter 2.0.0
05) Attendre que l'application (2.0.0) démarre
06) Générer une personne en appelant POST localhost:9991/person à la version 1.0.0
07) Générer une personne en appelant POST localhost:9992/person à la version 2.0.0
08) Tuer l'application (1.0.0)
09) Exécuter 3.0.0
10) Attendre que l'application (3.0.0) démarre
11) Générer une personne en appelant POST localhost:9992/person à la version 2.0.0
12) Générer une personne en appelant POST localhost:9993/person à la version 3.0.0
13) Tuer l'application (3.0.0)
14) Exécuter 4.0.0
15) Attendre que l'application (4.0.0) démarre
16) Générer une personne en appelant POST localhost:9993/person à la version 3.0.0
17) Générer une personne en appelant POST localhost:9994/person à la version 4.0.0

Démarrage de l'application en version 1.0.0
Générer une personne en version 1.0.0
Envoi d'un post à 127.0.0.1:9991/person. Voici la réponse :

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

Démarrage de l'application en version 2.0.0

Générer une personne en version 1.0.0
Envoi d'un post à 127.0.0.1:9991/person. Voici la réponse :

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

Générer une personne en version 2.0.0
Envoi d'un post à 127.0.0.1:9992/person. Voici la réponse :

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

Tuer l'application 1.0.0

Démarrage de l'application en version 3.0.0

Générer une personne en version 2.0.0
Envoi d'un post à 127.0.0.1:9992/person. Voici la réponse :
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Générer une personne en version 3.0.0
Envoi d'un post à 127.0.0.1:9993/person. Voici la réponse :
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Tuer l'application 2.0.0

Démarrage de l'application en version 4.0.0

Générer une personne en version 3.0.0
Envoi d'un post à 127.0.0.1:9993/person. Voici la réponse :

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

Générer une personne en version 4.0.0
Envoi d'un post à 127.0.0.1:9994/person. Voici la réponse :

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

Modifications de la BDD

Concernant v3 nous supprimons simplement la colonne last_name et ajoutons les contraintes manquantes.

-- SUPPRIMER LA COLONNE
ALTER TABLE PERSON DROP last_name;

-- AJOUTER DES CONTRAINTES
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Modifications de code

Il n'y a pas de modifications dans le code.

Sortie

Nous avons réussi à appliquer un changement de nom de colonne incompatible en effectuant plusieurs déploiements rétrocompatibles. Voici un résumé des actions effectuées :

  1. déploiement de l'application version 1.0.0 avec v1 schéma de la BDD (nom de la colonne = last_name)
  2. déploiement de l'application version 2.0.0, qui conserve les données dans last_name et surname. L'application lit depuis last_name. La base de données est en version v2, contenant des colonnes telles que last_name, et via nom. nom est une copie de l'last_name. (REMARQUE : cette colonne ne doit pas avoir de contrainte not null)
  3. déploiement de l'application version 3.0.0, qui conserve les données uniquement dans surname et lit depuis nom. En ce qui concerne la base de données, la dernière migration est en cours last_name dans surname. De plus, la contrainte NOT NULL est levée sur last_name. La base de données est maintenant en version v3
  4. déploiement de l'application version 4.0.0 — aucun changement de code n'est effectué. Le déploiement de la base de données v4, qui supprime last_name. Ici, vous pouvez ajouter toutes les contraintes manquantes dans la base de données.

En suivant cette approche, vous pouvez toujours revenir à une version précédente sans casser la compatibilité entre la base de données / l'application.

Code

Tout le code utilisé dans cet article est disponible sur Github. Ci-dessous, une description supplémentaire.

Projets

Après avoir cloné le référentiel, vous verrez la structure de dossiers suivante.

├── boot-flyway-v1              - version 1.0.0 de l'application avec v1 du schéma
├── boot-flyway-v2              - version 2.0.0 de l'application avec v2 du schéma (rétrocompatible - l'application peut être annulée)
├── boot-flyway-v2-bad          - version 2.0.0.BAD de l'application avec v2bad du schéma (non rétrocompatible - l'application ne peut pas être annulée)
├── boot-flyway-v3              - version 3.0.0 de l'application avec v3 du schéma (l'application peut être annulée)
└── boot-flyway-v4              - version 4.0.0 de l'application avec v4 du schéma (l'application peut être annulée)

Scripts

Vous pouvez exécuter les scénarios décrits dans les scripts ci-dessous, qui démontreront les modifications rétrocompatibles et non compatibles dans la base de données.

Pour voir un cas avec des modifications rétrocompatibles, exécutez :

./scripts/scenario_backward_compatible.sh

Et pour voir un cas avec des modifications non compatibles, exécutez :

./scripts/scenario_backward_incompatible.sh

Exemple de Spring Boot Flyway

Tous les exemples proviennent de Exemple de Spring Boot Flyway.

Vous pouvez consulter http://localhost:8080/flyway, il y a une liste de scripts.

Cet exemple inclut également la console H2 (à l'adresse http://localhost:8080/h2-console), pour que vous puissiez consulter l'état de la base de données (URL jdbc par défaut — jdbc:h2:mem:testdb).

Supplémentaire

Lisez aussi d'autres articles sur notre blog :

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster