Heim > Java > javaLernprogramm > Flyway-Migrationen in Gradle-Projekten mit mehreren Modulen (Clean Architecture)

Flyway-Migrationen in Gradle-Projekten mit mehreren Modulen (Clean Architecture)

Barbara Streisand
Freigeben: 2025-01-19 08:05:08
Original
578 Leute haben es durchsucht

Automatisierung von Datenbankmigrationen in Java mit Flyway

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

Grundlegendes zu Multiprojekt-Builds in Gradle

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.

gradle project

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)
Nach dem Login kopieren
Nach dem Login kopieren

(1) Die Datei „settings.gradle.kts“ sollte alle Unterprojekte enthalten.
(2) Jedes Unterprojekt sollte seine eigene build.gradle.kts-Datei haben.

Nutzung von Gradle-Submodulen für eine saubere Architektur

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:

  • Enthält Geschäftslogik, Domänenmodelle und Anwendungsregeln. Hat keine Abhängigkeit von Extern oder Web.
  • Sollte nach Möglichkeit unabhängig von Framework-spezifischen Implementierungen sein.

Extern:

  • Verwaltet externe Aktionen oder Integrationen, wie Datenbankmigrationen oder Interaktionen mit Drittanbieterdiensten.
  • Kann für die Geschäftslogik von Core abhängig sein, sollte aber nicht von Web abhängen.

Web:

  • Der Einstiegspunkt, der eine REST-API verfügbar macht und HTTP-Anfragen verarbeitet.
  • Hängt von Core für die Geschäftslogik ab und kann von External für Integrationen abhängen.
├── .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)
Nach dem Login kopieren
Nach dem Login kopieren

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
Nach dem Login kopieren

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()
    }
}
Nach dem Login kopieren

Erforderliche Abhängigkeiten für das „Web“-Projekt.

rootProject.name = "SchoolStaff"

include("Core", "External", "Web")
Nach dem Login kopieren

Erforderliche Abhängigkeiten für das „Core“-Projekt.

dependencies {
    implementation(project(":Core"))
    implementation(project(":External"))
}
Nach dem Login kopieren

Erforderliche Abhängigkeiten für das „Externe“ Projekt.

dependencies {
    runtimeOnly(project(":External"))
}
Nach dem Login kopieren

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"))
}
Nach dem Login kopieren

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"
}
Nach dem Login kopieren

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
}
Nach dem Login kopieren

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)
);
Nach dem Login kopieren

Dadurch werden alle Migrationsdateien auf die Datenbank angewendet.

Abschluss

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!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage