Datenbankmigrationen sind ein entscheidender Aspekt der Softwareentwicklung, insbesondere in Umgebungen, in denen kontinuierliche Integration und Bereitstellung (CI/CD) Standard sind. Wenn Ihre Anwendung wächst und sich weiterentwickelt, muss auch das Datenbankschema wachsen, von dem sie abhängt. Die manuelle Verwaltung dieser Schemaänderungen kann zu Fehlern führen und viel Zeit in Anspruch nehmen.
Da kommt Flyway ins Spiel, ein unschätzbar wertvolles Open-Source-Tool, das darauf zugeschnitten ist, Datenbankmigrationen zu vereinfachen. Flyway führt die Versionskontrolle in Ihre Datenbank ein, sodass Sie Ihr Schema sicher und zuverlässig migrieren können. In diesem Artikel erfahren Sie, wie Sie Datenbankmigrationen in Multi-Modul-Gragle-Java-Projekten mithilfe von Flyway automatisieren und so sicherstellen, dass die Verwaltung von Datenbankänderungen zu einem optimierten, fehlerresistenten Prozess wird.
Weitere Details zum Flyway
Während einige kleinere Projekte oder monolithische Anwendungen möglicherweise mit nur einer Build-Datei und einer einheitlichen Quellstruktur auskommen, sind größere Projekte häufig in mehreren, voneinander abhängigen Modulen organisiert. Der Begriff „interdependent“ ist hier von entscheidender Bedeutung und unterstreicht die Notwendigkeit, diese Module über einen einzigen Build-Prozess zu verbinden.
Gradle unterstützt dieses Setup mit seiner Multiprojekt-Build-Fähigkeit, die oft als Multimodulprojekt bezeichnet wird. In der Terminologie von Gradle werden diese Module Teilprojekte genannt.
Ein Multiprojekt-Build ist um ein Stammprojekt herum strukturiert und kann mehrere Unterprojekte darunter enthalten.
Die Verzeichnisstruktur sollte wie folgt aussehen:
├── .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) Die Datei „settings.gradle.kts“ sollte alle Unterprojekte enthalten.
(2) Jedes Unterprojekt sollte seine eigene build.gradle.kts-Datei haben.
Clean Architecture ist ein Entwurfsmuster, das die Trennung von Belangen betont und so die Wartung und das Testen von Software erleichtert. Eine der praktischen Möglichkeiten, diese Architektur in einem Projekt zu implementieren, besteht darin, die Untermodulstruktur von Gradle zum Organisieren Ihrer Codebasis zu verwenden. So können Sie Clean Architecture mit Gradle-Untermodulen ausrichten:
Saubere Architekturebenen:
Kern:
Extern:
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)
Schritt 1: Erstellen Sie ein Java-basiertes Gradle-Projekt und nennen Sie es „SchoolStaff“.
Schritt 2: Gehen Sie zu Spring Initializr und generieren Sie ein REST-API-Projekt mit dem Namen Web.
Schritt 3: Erstellen Sie ein Java-basiertes Gradle-Projekt und nennen Sie es Extern.
Schritt 4: Erstellen Sie ein Java-basiertes Gradle-Projekt und nennen Sie es Core.
Root 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() } }
Erforderliche Abhängigkeiten für das „Web“-Projekt.
rootProject.name = "SchoolStaff" include("Core", "External", "Web")
Erforderliche Abhängigkeiten für das „Core“-Projekt.
dependencies { implementation(project(":Core")) implementation(project(":External")) }
Erforderliche Abhängigkeiten für das „Externe“ Projekt.
dependencies { runtimeOnly(project(":External")) }
Wir verwenden das folgende Plugin für die Flyway-Migration:
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")) }
Dieser Ansatz eignet sich gut für Produktionsumgebungen, da er kontrollierte und zuverlässige Migrationen gewährleistet. Anstatt Migrationen automatisch bei jedem Anwendungsstart durchzuführen, führen wir sie nur bei Bedarf aus, was für mehr Flexibilität und Kontrolle sorgt.
Wir verwenden auch die Datei application.properties in der Spring-Anwendung, um Datenbankverbindungen und Anmeldeinformationen zu verwalten. Die Einstellung „baselineOnMigrate = true“ stellt sicher, dass die anfängliche Migration als Basis für zukünftige Migrationen verwendet wird.
plugins { id("org.flywaydb.flyway") version "11.0.1" }
Wir können JPA Buddy verwenden, um alle Migrationsdateien im Verzeichnis resources/db/migration des externen Projekts zu generieren.
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 }
Vom Root-Projekt aus können wir die Flyway-Migration mit dem folgenden Befehl ausführen:
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) );
Dadurch werden alle Migrationsdateien auf die Datenbank angewendet.
Wir haben untersucht, wie Datenbankmigrationen mithilfe von Flyway innerhalb eines Gradle-Multimodulprojekts automatisiert werden können, was für die Aufrechterhaltung der Schemakonsistenz in CI/CD-Umgebungen von entscheidender Bedeutung ist.
Wir haben auch behandelt, wie Gradle Multiprojekt-Builds unterstützt und komplexe Projekte in überschaubare Unterprojekte organisiert, jedes mit seiner eigenen Build-Konfiguration, vereinheitlicht unter einem Root-Build-Skript.
Zuletzt haben wir Clean Architecture mit Gradle-Modulen in Einklang gebracht und das Projekt in Core-, External- und Web-Schichten strukturiert, um eine saubere Trennung von Anliegen und Abhängigkeitsmanagement zu fördern.
Diese Praktiken verbessern die Modularität, Automatisierung und Wartbarkeit und schaffen die Voraussetzungen für eine skalierbare, fehlerfreie Softwareentwicklung.
Das obige ist der detaillierte Inhalt vonFlyway-Migrationen in Gradle-Projekten mit mehreren Modulen (Clean Architecture). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!