1. Überblick über den Migrationsmechanismus

1.1 Konzept und Rolle der Migration

Datenbankmigration ist der Prozess der Synchronisierung von Änderungen in Datenmodellen mit der Datenbankstruktur, was für die Datenpersistenz entscheidend ist. Mit jeder neuen Version der Anwendung werden häufig Änderungen am Datenmodell vorgenommen, wie das Hinzufügen oder Löschen von Feldern oder das Modifizieren von Indizes. Die Migration ermöglicht es Entwicklern, diese Änderungen auf versionierte und systematische Weise zu verwalten, um die Konsistenz zwischen der Datenbankstruktur und dem Datenmodell sicherzustellen.

In der modernen Webentwicklung bietet der Migrationsmechanismus folgende Vorteile:

  1. Versionskontrolle: Migrationsdateien können die Änderungshistorie der Datenbankstruktur verfolgen, was es bequem macht, Änderungen in jeder Version zurückzuverfolgen und zu verstehen.
  2. Automatisierte Bereitstellung: Durch den Migrationsmechanismus können Datenbankbereitstellung und -aktualisierungen automatisiert werden, was die Möglichkeit manueller Eingriffe und das Risiko von Fehlern reduziert.
  3. Teamarbeit: Migrationsdateien stellen sicher, dass Teammitglieder in verschiedenen Entwicklungsumgebungen synchronisierte Datenbankstrukturen verwenden, was die gemeinsame Entwicklung erleichtert.

1.2 Migrationsfunktionen des ent-Frameworks

Die Integration des ent-Frameworks mit dem Migrationsmechanismus bietet folgende Funktionen:

  1. Deklarative Programmierung: Entwickler müssen sich nur auf die Go-Repräsentation der Entitäten konzentrieren, und das ent-Framework wird die Konvertierung der Entitäten in Datenbanktabellen übernehmen.
  2. Automatische Migration: ent kann automatisch Datenbanktabellenstrukturen erstellen und aktualisieren, ohne dass manuell DDL-Anweisungen geschrieben werden müssen.
  3. Flexible Kontrolle: ent bietet verschiedene Konfigurationsoptionen zur Unterstützung unterschiedlicher Migrationsanforderungen, z.B. mit oder ohne Fremdschlüsselbeschränkungen und zur Generierung global eindeutiger IDs.

2. Einführung in die automatische Migration

2.1 Grundprinzipien der automatischen Migration

Die automatische Migrationsfunktion des ent-Frameworks basiert auf den Schemadefinitionsdateien (typischerweise im Verzeichnis ent/schema), um die Datenbankstruktur zu generieren. Nachdem Entwickler Entitäten und Beziehungen definiert haben, überprüft ent die vorhandene Struktur in der Datenbank und generiert entsprechende Operationen zum Erstellen von Tabellen, Hinzufügen oder Modifizieren von Spalten, Erstellen von Indizes usw.

Darüber hinaus funktioniert das automatische Migrationsprinzip von ent im "Anhänge-Modus": Es fügt standardmäßig nur neue Tabellen, neue Indizes oder Spalten zu Tabellen hinzu und löscht keine vorhandenen Tabellen oder Spalten. Diese Gestaltung ist vorteilhaft, um versehentlichen Datenverlust zu verhindern und die Datenbankstruktur einfach in vorwärtsgerichteter Weise zu erweitern.

2.2 Verwendung der automatischen Migration

Die Grundschritte zur Verwendung der automatischen Migration von ent sind wie folgt:

package main

import (
    "context"
    "log"
    "ent"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("Verbindung zu MySQL fehlgeschlagen: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // Führen Sie die automatische Migration aus, um das Datenbankschema zu erstellen oder zu aktualisieren
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("Fehler beim Erstellen des Datenbankschemas: %v", err)
    }
}

Im obigen Code ist ent.Open für den Aufbau einer Verbindung zur Datenbank und die Rückgabe einer Client-Instanz verantwortlich, während client.Schema.Create die tatsächliche automatische Migrationsoperation ausführt.

3. Fortgeschrittene Anwendungen der automatischen Migration

3.1 Löschen von Spalten und Indizes

In einigen Fällen müssen möglicherweise Spalten oder Indizes, die nicht mehr benötigt werden, aus dem Datenbankschema entfernt werden. Zu diesem Zeitpunkt können die Optionen WithDropColumn und WithDropIndex verwendet werden. Zum Beispiel:

// Migration mit Optionen zum Löschen von Spalten und Indizes ausführen.
err = client.Schema.Create(
    ctx,
    migrate.WithDropIndex(true),
    migrate.WithDropColumn(true),
)

Dieser Codeausschnitt ermöglicht die Konfiguration zum Löschen von Spalten und Indizes während der automatischen Migration. ent löscht alle Spalten und Indizes, die nicht in der Schemadefinition existieren, wenn die Migration ausgeführt wird.

3.2 Globale eindeutige ID

Standardmäßig beginnen die Primärschlüssel in SQL-Datenbanken für jede Tabelle bei 1, und verschiedene Entitätstypen können dieselbe ID teilen. In einigen Anwendungsszenarien, z.B. bei der Verwendung von GraphQL, ist es möglicherweise erforderlich, globale Eindeutigkeit für die IDs von Objekten verschiedener Entitätstypen bereitzustellen. In ent kann dies mit der Option WithGlobalUniqueID konfiguriert werden:

// Migration mit global eindeutigen IDs für jede Entität ausführen.
if err := client.Schema.Create(ctx, migrate.WithGlobalUniqueID(true)); err != nil {
    log.Fatalf("Fehler beim Erstellen des Datenbankschemas: %v", err)
}

Nach Aktivierung der Option WithGlobalUniqueID weist ent jeder Entität in einer Tabelle namens ent_types einen ID-Bereich von 2^32 zu, um globale Eindeutigkeit zu erreichen.

3.3 Offline-Modus

Der Offline-Modus ermöglicht es, Schemaänderungen nicht direkt in der Datenbank auszuführen, sondern sie stattdessen in einen io.Writer zu schreiben. Das ist nützlich, um SQL-Befehle zu überprüfen, bevor die Änderungen wirksam werden, oder um ein SQL-Skript für die manuelle Ausführung zu generieren. Zum Beispiel:

// Migrationsschritte in eine Datei schreiben
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("Fehler beim Erstellen der Migrationsdatei: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("Fehler beim Schreiben der Migrations-Schemaänderungen: %v", err)
}

Dieser Code schreibt die Migrationsänderungen in eine Datei namens migrate.sql. In der Praxis können Entwickler wählen, ob sie direkt auf die Standardausgabe drucken oder die Änderungen zur Überprüfung oder Archivierung in eine Datei schreiben.

4. Unterstützung für Fremdschlüssel und benutzerdefinierte Hooks

4.1 Aktivieren oder Deaktivieren von Fremdschlüsseln

In Ent werden Fremdschlüssel durch die Definition von Beziehungen (Edges) zwischen Entitäten implementiert, und diese Fremdschlüsselbeziehungen werden automatisch auf Datenbankebene erstellt, um die Datenintegrität und -konsistenz durchzusetzen. In bestimmten Situationen, wie z.B. zur Leistungsoptimierung oder wenn die Datenbank keine Fremdschlüssel unterstützt, können Sie diese deaktivieren.

Um Fremdschlüsselbedingungen in Migrationen zu aktivieren oder zu deaktivieren, können Sie dies über die Konfigurationsoption WithForeignKeys steuern:

// Fremdschlüssel aktivieren
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true), 
)
if err != nil {
    log.Fatalf("Fehler beim Erstellen von Schemaresourcen mit Fremdschlüsseln: %v", err)
}

// Fremdschlüssel deaktivieren
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false), 
)
if err != nil {
    log.Fatalf("Fehler beim Erstellen von Schemaresourcen ohne Fremdschlüssel: %v", err)
}

Diese Konfigurationsoption muss beim Aufruf von Schema.Create übergeben werden und bestimmt, ob Fremdschlüsselbedingungen im generierten DDL basierend auf dem angegebenen Wert enthalten sein sollen.

4.2 Anwendung von Migrations-Hooks

Migrations-Hooks sind benutzerdefinierte Logiken, die an verschiedenen Stellen der Migrationsausführung eingefügt und ausgeführt werden können. Sie sind sehr nützlich, um spezifische Logik in der Datenbank vor/nach der Migration auszuführen, wie z.B. die Validierung von Migrationsergebnissen und das Vorabfüllen von Daten.

Hier ist ein Beispiel, wie benutzerdefinierte Migrations-Hooks implementiert werden können:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // Benutzerdefinierter Code, der vor der Migration ausgeführt werden soll
        // Zum Beispiel Logging, Überprüfung bestimmter Voraussetzungen usw.
        log.Println("Benutzerdefinierte Logik vor der Migration")
        
        // Den nächsten Hook oder die standardmäßige Migrationslogik aufrufen
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }
        
        // Benutzerdefinierter Code, der nach der Migration ausgeführt werden soll
        // Zum Beispiel Bereinigung, Datenmigration, Sicherheitsüberprüfungen usw.
        log.Println("Benutzerdefinierte Logik nach der Migration")
        return nil
    })
}

// Verwendung von benutzerdefinierten Hooks in der Migration
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("Fehler beim Anwenden von benutzerdefinierten Migrations-Hooks: %v", err)
}

Hooks sind leistungsstarke und unverzichtbare Werkzeuge für komplexe Migrationen, die Ihnen die direkte Kontrolle über das Migrationsverhalten der Datenbank geben, wenn dies erforderlich ist.

5. Versionierte Migrationen

5.1 Einführung in versionierte Migrationen

Versionierte Migration ist ein Muster zur Verwaltung von Datenbankmigrationen, das es Entwicklern ermöglicht, Änderungen an der Datenbankstruktur in mehrere Versionen aufzuteilen, von denen jede eine bestimmte Reihe von Befehlen zur Datenbankmodifikation enthält. Im Vergleich zur automatischen Migration bietet die versionierte Migration eine feinere Kontrolle, die die Rückverfolgbarkeit und Umkehrbarkeit von Datenbankstrukturänderungen sicherstellt.

Der Hauptvorteil der versionierten Migration ist die Unterstützung von Vorwärts- und Rückwärtsmigrationen (d.h. Upgrade oder Downgrade), die es Entwicklern ermöglicht, spezifische Änderungen anzuwenden, rückgängig zu machen oder zu überspringen, wenn dies erforderlich ist. Bei der Zusammenarbeit in einem Team stellt versionierte Migration sicher, dass jedes Mitglied an derselben Datenbankstruktur arbeitet und reduziert Probleme durch Inkonsistenzen.

Automatische Migration ist oft nicht umkehrbar und erzeugt und führt SQL-Anweisungen aus, um den neuesten Stand der Entitätsmodelle abzugleichen, hauptsächlich in Entwicklungsphasen oder kleinen Projekten.

5.2 Verwendung von versionierten Migrationen

1. Atlas-Tool installieren

Bevor Sie versionierte Migrationen verwenden, müssen Sie das Atlas-Tool auf Ihrem System installieren. Atlas ist ein Migrationstool, das mehrere Datenbanksysteme unterstützt und leistungsstarke Funktionen zur Verwaltung von Datenbankschemawechseln bietet.

macOS + Linux

curl -sSf https://atlasgo.sh | sh

Homebrew

brew install ariga/tap/atlas

Docker

docker pull arigaio/atlas
docker run --rm arigaio/atlas --help

Windows

https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe

2. Generieren von Migrationsdateien basierend auf aktuellen Entitätsdefinitionen

atlas migrate diff migration_name \
  --dir "file://ent/migrate/migrations" \
  --to "ent://ent/schema" \
  --dev-url "docker://mysql/8/ent"

3. Anwendungs-Migrationsdateien

Sobald die Migrationsdateien generiert sind, können sie auf die Entwicklungs-, Test- oder Produktionsumgebungen angewendet werden. Normalerweise würden Sie diese Migrationsdateien zunächst auf eine Entwicklungs- oder Testdatenbank anwenden, um sicherzustellen, dass sie wie erwartet ausgeführt werden. Anschließend würden dieselben Migrationschritte in der Produktionsumgebung ausgeführt.

atlas migrate apply \
  --dir "file://ent/migrate/migrations" \
  --url "mysql://root:pass@localhost:3306/example"

Verwenden Sie den Befehl atlas migrate apply, indem Sie das Verzeichnis der Migrationsdateien (--dir) und die URL der Ziel-Datenbank (--url) angeben, um die Migrationsdateien anzuwenden.