Maison >Java >javaDidacticiel >Migrations de voies de migration dans les projets Gradle multi-modules (architecture propre)
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
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.
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.
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 :
Externe :
Web :
├── .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.
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!