Maison >Java >javaDidacticiel >Migrations de voies de migration dans les projets Gradle multi-modules (architecture propre)

Migrations de voies de migration dans les projets Gradle multi-modules (architecture propre)

Barbara Streisand
Barbara Streisandoriginal
2025-01-19 08:05:08580parcourir

Automatisation des migrations de bases de données en Java avec Flyway

Les migrations de bases de données sont un aspect crucial du développement logiciel, en particulier dans les environnements où l'intégration et la livraison continues (CI/CD) sont une pratique courante. À mesure que votre application grandit et évolue, le schéma de base de données dont elle dépend doit également évoluer. La gestion manuelle de ces modifications de schéma peut entraîner des erreurs et prendre beaucoup de temps.

Entrez dans Flyway, un outil open source inestimable conçu pour simplifier les migrations de bases de données. Flyway introduit le contrôle de version dans votre base de données, vous permettant de migrer votre schéma en toute sécurité et avec fiabilité. Dans cet article, nous explorerons comment automatiser les migrations de bases de données dans un projet Java Gragle multi-modules à l'aide de Flyway, garantissant que la gestion des modifications de la base de données devienne un processus rationalisé et résistant aux erreurs.

Plus de détails sur la voie de migration

Comprendre les builds multi-projets dans Gradle

Alors que certains projets plus petits ou applications monolithiques peuvent fonctionner avec un seul fichier de construction et une structure source unifiée, les projets plus importants sont souvent organisés en plusieurs modules interdépendants. Le terme « interdépendant » est ici clé, soulignant la nécessité de connecter ces modules via un processus de construction unique.

Gradle répond à cette configuration avec sa capacité de construction multi-projets, souvent qualifiée de projet multi-module. Dans la terminologie de Gradle, ces modules sont appelés sous-projets.

Une construction multi-projets est structurée autour d'un projet racine et peut inclure plusieurs sous-projets en dessous.

gradle project

La structure des répertoires devrait ressembler à ceci :

├── .gradle
│   └── ⋮
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
├── gradlew
├── gradlew.bat
├── settings.gradle.kts (1)
├── sub-project-1
│   └── build.gradle.kts (2) 
├── sub-project-2
│   └── build.gradle.kts (2) 
└── sub-project-3
    └── build.gradle.kts (2)

(1) Le fichier settings.gradle.kts doit inclure tous les sous-projets.
(2) Chaque sous-projet doit avoir son propre fichier build.gradle.kts.

Tirer parti des sous-modules Gradle pour une architecture propre

L'architecture propre est un modèle de conception qui met l'accent sur la séparation des préoccupations, rendant les logiciels plus faciles à maintenir et à tester. L'un des moyens pratiques de mettre en œuvre cette architecture dans un projet consiste à utiliser la structure de sous-modules de Gradle pour organiser votre base de code. Voici comment aligner Clean Architecture avec les sous-modules Gradle :

Couches d'architecture propres :
Noyau :

  • Contient la logique métier, les modèles de domaine et les règles d'application. N'a aucune dépendance vis-à-vis de l'externe ou du Web.
  • Devrait être indépendant des implémentations spécifiques au framework lorsque cela est possible.

Externe :

  • Gère les actions ou intégrations externes, telles que les migrations de bases de données ou les interactions avec des services tiers.
  • Peut dépendre de Core pour la logique métier mais ne devrait pas dépendre du Web.

Web :

  • Le point d'entrée, exposant une API REST et gérant les requêtes HTTP.
  • Dépend de Core pour la logique métier et peut dépendre d'External pour les intégrations.
├── .gradle
│   └── ⋮
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
├── gradlew
├── gradlew.bat
├── settings.gradle.kts (1)
├── sub-project-1
│   └── build.gradle.kts (2) 
├── sub-project-2
│   └── build.gradle.kts (2) 
└── sub-project-3
    └── build.gradle.kts (2)

Étape 1 : Créez un projet Gradle basé sur Java et nommez-le « SchoolStaff ».

Étape 2 : Accédez à Spring Initializr et générez un projet API REST nommé Web.

Étape 3 : Créez un projet Gradle basé sur Java et nommez-le Externe.

Étape 4 : Créez un projet Gradle basé sur Java et nommez-le Core.

Racine build.gradle.kts

SchoolStaff/
├── Core/
│   ├── src/
│   │   └── main/
│   │       ├── java/         # Business logic and domain objects
│   │       └── resources/    # Core-specific resources (if any)
│   └── build.gradle.kts
├── External/
│   ├── src/
│   │   └── main/
│   │       ├── java/         # External integration code
│   │       └── resources/    # db/migration and other external resources
│   └── build.gradle.kts
├── Web/
│   ├── src/
│   │   └── main/
│   │       ├── java/         # REST controllers and entry-point logic
│   │       └── resources/    # Application-specific configuration
│   └── build.gradle.kts
├── build.gradle.kts          # Root Gradle build
└── settings.gradle.kts       # Project module settings

settings.gradle.kts

plugins {
    id("java")
}

allprojects {
    group = "school.staff"
    version = "1.0.0"

    repositories {
        mavenLocal()
        mavenCentral()
    }
}

subprojects {
    apply(plugin = "java")

    dependencies {
        testImplementation(platform("org.junit:junit-bom:5.10.0"))
        testImplementation("org.junit.jupiter:junit-jupiter")
    }

    tasks.test {
        useJUnitPlatform()
    }
}

Dépendances requises pour le projet "Web".

rootProject.name = "SchoolStaff"

include("Core", "External", "Web")

Dépendances requises pour le projet "Core".

dependencies {
    implementation(project(":Core"))
    implementation(project(":External"))
}

Dépendances requises pour le projet "Externe".

dependencies {
    runtimeOnly(project(":External"))
}

Nous utilisons le plugin suivant pour la migration Flyway :

import java.sql.DriverManager
import java.util.Properties
// Function to load properties based on the environment
fun loadProperties(env: String): Properties {
    val properties = Properties()
    val propsFile = file("../web/src/main/resources/application-$env.properties")

    if (propsFile.exists()) {
        propsFile.inputStream().use { properties.load(it) }
    } else {
        throw GradleException("Properties file for environment '$env' not found: ${propsFile.absolutePath}")
    }

    return properties
}
// Set the environment (default is 'dev' if no argument is passed)
val env = project.findProperty("env")?.toString() ?: "dev"

// Load properties for the chosen environment
val dbProps = loadProperties(env)
buildscript {
    dependencies {
        classpath("org.flywaydb:flyway-database-postgresql:11.1.0") // This is required for the flyway plugin to work on the migration, otherwise it will throw an error as No Database found
        classpath("org.postgresql:postgresql:42.7.4")
    }
}
plugins {
    id("java-library")
    id("org.flywaydb.flyway") version "11.0.1"
}

group = "school.staff"
version = "unspecified"

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa:3.4.0")
    implementation("org.postgresql:postgresql:42.7.4")
    implementation("org.flywaydb:flyway-core:11.0.1")
    implementation("org.flywaydb:flyway-database-postgresql:11.0.1")
    implementation("org.flywaydb:flyway-gradle-plugin:11.0.1")

    implementation (project(":Core"))
    testImplementation(platform("org.junit:junit-bom:5.10.0"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

tasks.test {
    useJUnitPlatform()
}

// Task to create the database if it doesn't exist
tasks.register("createDatabase") {
    doLast {
        val dbUrl = dbProps["spring.datasource.url"] as String
        val dbUsername = dbProps["spring.datasource.username"] as String
        val dbPassword = dbProps["spring.datasource.password"] as String

        // Extract the base URL and database name
        val baseDbUrl = dbUrl.substringBeforeLast("/")+ "/"
        val dbName = dbUrl.substringAfterLast("/")

        // Connect to the PostgreSQL server (without the specific database)
        DriverManager.getConnection(baseDbUrl, dbUsername, dbPassword).use { connection ->
            val stmt = connection.createStatement()
            val resultSet = stmt.executeQuery("SELECT 1 FROM pg_database WHERE datname = '$dbName'")
            if (!resultSet.next()) {
                println("Database '$dbName' does not exist. Creating it...")
                stmt.executeUpdate("CREATE DATABASE \"$dbName\"")
                println("Database '$dbName' created successfully.")
            } else {
                println("Database '$dbName' already exists.")
            }
        }
    }
}

flyway {
    url = dbProps["spring.datasource.url"] as String
    user = dbProps["spring.datasource.username"] as String
    password = dbProps["spring.datasource.password"] as String
    locations = arrayOf("classpath:db/migration")
    baselineOnMigrate = true
}

 //Ensure classes are built before migration
tasks.named("flywayMigrate").configure {
    dependsOn(tasks.named("createDatabase"))
    dependsOn(tasks.named("classes"))
}

Cette approche est bien adaptée aux environnements de production, car elle garantit des migrations contrôlées et fiables. Au lieu d'exécuter automatiquement les migrations à chaque démarrage d'application, nous les exécutons uniquement lorsque cela est nécessaire, offrant ainsi une plus grande flexibilité et un meilleur contrôle.

Nous utilisons également le fichier application.properties dans l'application Spring pour gérer les connexions à la base de données et les informations d'identification. Le paramètre baselineOnMigrate = true garantit que la migration initiale est utilisée comme référence pour les migrations futures.

 plugins {
    id("org.flywaydb.flyway") version "11.0.1"
}

Nous pouvons utiliser JPA Buddy pour générer tous les fichiers de migration dans le répertoire resources/db/migration du projet externe.

V1__Initial_Migration

flyway {
    url = dbProps["spring.datasource.url"] as String
    user = dbProps["spring.datasource.username"] as String
    password = dbProps["spring.datasource.password"] as String
    locations = arrayOf("classpath:db/migration")
    baselineOnMigrate = true
}

Depuis le projet racine, nous pouvons exécuter la migration Flyway à l'aide de la commande suivante :

CREATE TABLE _user
(
    id                 UUID NOT NULL,
    created_by         UUID,
    created_date       TIMESTAMP WITH TIME ZONE,
    last_modified_by   UUID,
    last_modified_date TIMESTAMP WITH TIME ZONE,
    first_name         VARCHAR(255),
    last_name          VARCHAR(255),
    email              VARCHAR(255),
    password           VARCHAR(255),
    tenant_id          UUID,
    CONSTRAINT pk__user PRIMARY KEY (id)
);

Cela appliquera tous les fichiers de migration à la base de données.

Conclusion

Nous avons exploré comment automatiser les migrations de bases de données à l'aide de Flyway dans un projet multi-module Gradle, ce qui est crucial pour maintenir la cohérence des schémas dans les environnements CI/CD.

Nous avons également expliqué comment Gradle prend en charge les builds multi-projets, en organisant les projets complexes en sous-projets gérables, chacun avec sa propre configuration de build, unifiés sous un script de build racine.

Enfin, nous avons aligné Clean Architecture avec les modules Gradle, structurant le projet en couches Core, Externe et Web, favorisant une séparation nette des préoccupations et de la gestion des dépendances.

Ces pratiques améliorent la modularité, l'automatisation et la maintenabilité, ouvrant la voie à un développement logiciel évolutif et sans erreur.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn