Heim  >  Artikel  >  Backend-Entwicklung  >  So verwenden Sie Migrationen mit Golang

So verwenden Sie Migrationen mit Golang

Linda Hamilton
Linda HamiltonOriginal
2024-11-09 07:28:02854Durchsuche

Einfache Beispielanwendung, die die Verwendung von golang-migrate zeigt

Warum sollten Sie Migrationen nutzen?

Viele Leute stellen diese Frage und ich habe versucht, diese Liste zu erstellen, um die wichtigsten Vorteile der Verwendung von Migrationen hervorzuheben:

Versionskontrolle: Eine der wichtigsten und wichtigsten ist die Möglichkeit, eine Versionierung der verschiedenen Änderungen des Datenbankschemas zu ermöglichen. Ohne die Migrationen wären diese Schemaänderungen inkohärent und könnten nicht nachverfolgt werden, was zu Versionierungsproblemen und möglichen Fehlern führen würde.

Rollback: Für den Fall eines Fehlers ist immer ein Rollback-System erforderlich. Ein Migrationssystem verfügt immer über zwei Methoden: oben, um die Änderungen in der Datenbank anzuwenden, und unten, um die Änderungen schnell und konsistent rückgängig zu machen :-)

Automatisierung und CI/CD-Integration: Migrationen können automatisiert werden, sodass sie Teil der CI/CD-Pipeline sein können. Dies hilft bei der reibungslosen und konsistenten Bereitstellung von Änderungen ohne manuellen Eingriff.

Wir können noch viele weitere Vorteile finden, aber ich denke, diese Punkte stellen eine gute Zusammenfassung der Hauptvorteile dar.

Wie implementiert man Migrationen in Golang?

Go unterstützt Migrationen aus diesem Grund nicht nativ, daher können wir das beliebte Paket golang-migrate verwenden. Auch wenn Sie ein ORM wie GORM verwenden, können Sie es dafür verwenden.

Beide Pakete sind sehr beliebt, aber in diesem Beispiel verwende ich golang-migrate, da ich kein Interesse an der Implementierung eines ORM habe.

Zeig mir den Code!

Sehen wir uns Schritt für Schritt an, wie eine einfache Anwendung implementiert wird, um zu sehen, wie sie verwendet wird.

Um diesem Artikel folgen zu können, benötigen Sie: Go und Docker mit Docker Compose

Infrastruktur

Erstellen Sie die Datei docker-compose.yml in Ihrem Stammverzeichnis, in dem wir Ihre Lieblings-DB definieren. In meinem Fall verwenden Sie eine MariaDB, Sie können aber auch gerne eine andere verwenden.

services:
  mariadb:
    image: mariadb:11.5.2
    container_name: mariadb_example_go_migration
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE=app
      - MYSQL_ROOT_PASSWORD=root
      - TZ=Europe/Berlin
    volumes:
      - mariadbdata:/var/lib/mysql

volumes:
  mariadbdata:
    driver: local

docker compose up -d 

Wenn Sie möchten, können Sie Docker direkt anstelle von Docker-Compose verwenden:

docker volume create -d local mariadbdata
docker run --name mariadb_example_go_migration -p 3306:3306 -e MYSQL_DATABASE=app -e MYSQL_ROOT_PASSWORD=root -e TZ=Europe/Berlin -v mariadbdata:/var/lib/mysql mariadb:11.5.2

Umweltwerte

Erstellen oder aktualisieren Sie die Datei .env in Ihrem Stammverzeichnis, in dem Sie die Variablen definieren müssen, um unsere Datenbank zu verbinden.

DATABASE_DSN=root:root@tcp(localhost:3306)/app

Erstellen Sie eine einfache Golang-App

Erstellen Sie eine einfache Golang-Anwendung, um eine erfolgreiche DB-Verbindung sicherzustellen und alle Tabellen und Strukturen in der Datenbank mit ihrer Struktur aufzulisten. cmd/main.go

package main

import (
 "database/sql"
 "fmt"
 "log"
 "os"
 "text/tabwriter"

 _ "github.com/go-sql-driver/mysql"
 "github.com/joho/godotenv"
)

func main() {
 // Load .env variables
 err := godotenv.Load()
 if err != nil {
  log.Fatal("Error loading .env file")
 }

 // Open connection with MySQL DB
 db, err := sql.Open("mysql", os.Getenv("DATABASE_DSN"))
 if err != nil {
  log.Fatalf("Error opening database: %v\n", err)
 }
 defer db.Close()

 // Ensure that the connection works
 err = db.Ping()
 if err != nil {
  log.Fatalf("Error connecting database: %v\n", err)
 }

 fmt.Println("Connected to database")

 // Execute the SHOW TABLES query to list all tables in the database
 tables, err := db.Query("SHOW TABLES")
 if err != nil {
  log.Fatalf("Failed to execute SHOW TABLES query: %v\n", err)
 }
 defer tables.Close()

 fmt.Println("Database structure:")

 for tables.Next() {
  var tableName string
  if err := tables.Scan(&tableName); err != nil {
   log.Fatalf("Failed to scan table name: %v\n", err)
  }

  w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)

  fmt.Printf("\n[Table: %s]\n\n", tableName)
  fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n", "Field", "Type", "Null", "Key", "Default", "Extra")

  // Get the structure of the current table
  structureQuery := fmt.Sprintf("DESCRIBE %s", tableName)
  columns, err := db.Query(structureQuery)
  if err != nil {
   log.Fatalf("Failed to describe table %s: %v\n", tableName, err)
  }
  defer columns.Close()

  for columns.Next() {
   var field, colType, null, key, defaultVal, extra sql.NullString
   err := columns.Scan(&field, &colType, &null, &key, &defaultVal, &extra)
   if err != nil {
    log.Fatalf("Failed to scan column: %v\n", err)
   }

   fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n",
    field.String, colType.String, null.String, key.String, defaultVal.String, extra.String)
  }

  w.Flush()
 }
}

Und wenn wir es ausführen, erhalten wir eine ähnliche Ausgabe:

How to use migrations with Golang

CLI migrieren

Um die Golang-Migrate-CLI auszuführen, haben Sie grundsätzlich zwei Methoden: Installieren Sie die CLI lokal oder führen Sie sie über das offizielle Docker-Image aus: migrieren/migrieren.

Persönlich bevorzuge ich die Docker-Variante, aber in diesem Tutorial werden beide Varianten veranschaulicht.

So generieren Sie eine Migration

Der erste Schritt besteht darin, mit dem nächsten Befehl eine leere Migration zu erstellen.

services:
  mariadb:
    image: mariadb:11.5.2
    container_name: mariadb_example_go_migration
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE=app
      - MYSQL_ROOT_PASSWORD=root
      - TZ=Europe/Berlin
    volumes:
      - mariadbdata:/var/lib/mysql

volumes:
  mariadbdata:
    driver: local

docker compose up -d 
docker volume create -d local mariadbdata
docker run --name mariadb_example_go_migration -p 3306:3306 -e MYSQL_DATABASE=app -e MYSQL_ROOT_PASSWORD=root -e TZ=Europe/Berlin -v mariadbdata:/var/lib/mysql mariadb:11.5.2
  • ext: Erweiterung der zu generierenden Datei.
  • dir: Verzeichnis, in dem unsere Migration erstellt wird.
  • seq: Name der Migrationssequenz.

Dieser Befehl generiert zwei leere Dateien im Ordner „database/migrations/“: 000001createuserstable.up.sql und 000001createuserstable.down.sql

In der Datei 000001createuserstable.up.sql definieren Sie SQL zum Erstellen einer Tabelle für Benutzer:

DATABASE_DSN=root:root@tcp(localhost:3306)/app

In der Datei 000001createuserstable.down.sql definieren Sie SQL, um alle von up vorgenommenen Änderungen rückgängig zu machen. In diesem Fall müssen wir die Benutzertabelle löschen:

package main

import (
 "database/sql"
 "fmt"
 "log"
 "os"
 "text/tabwriter"

 _ "github.com/go-sql-driver/mysql"
 "github.com/joho/godotenv"
)

func main() {
 // Load .env variables
 err := godotenv.Load()
 if err != nil {
  log.Fatal("Error loading .env file")
 }

 // Open connection with MySQL DB
 db, err := sql.Open("mysql", os.Getenv("DATABASE_DSN"))
 if err != nil {
  log.Fatalf("Error opening database: %v\n", err)
 }
 defer db.Close()

 // Ensure that the connection works
 err = db.Ping()
 if err != nil {
  log.Fatalf("Error connecting database: %v\n", err)
 }

 fmt.Println("Connected to database")

 // Execute the SHOW TABLES query to list all tables in the database
 tables, err := db.Query("SHOW TABLES")
 if err != nil {
  log.Fatalf("Failed to execute SHOW TABLES query: %v\n", err)
 }
 defer tables.Close()

 fmt.Println("Database structure:")

 for tables.Next() {
  var tableName string
  if err := tables.Scan(&tableName); err != nil {
   log.Fatalf("Failed to scan table name: %v\n", err)
  }

  w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)

  fmt.Printf("\n[Table: %s]\n\n", tableName)
  fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n", "Field", "Type", "Null", "Key", "Default", "Extra")

  // Get the structure of the current table
  structureQuery := fmt.Sprintf("DESCRIBE %s", tableName)
  columns, err := db.Query(structureQuery)
  if err != nil {
   log.Fatalf("Failed to describe table %s: %v\n", tableName, err)
  }
  defer columns.Close()

  for columns.Next() {
   var field, colType, null, key, defaultVal, extra sql.NullString
   err := columns.Scan(&field, &colType, &null, &key, &defaultVal, &extra)
   if err != nil {
    log.Fatalf("Failed to scan column: %v\n", err)
   }

   fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n",
    field.String, colType.String, null.String, key.String, defaultVal.String, extra.String)
  }

  w.Flush()
 }
}

So wenden Sie die Migration an

Der folgende Befehl wendet alle ausstehenden Migrationen an. Sie können auch die Anzahl der anzuwendenden Migrationen definieren, indem Sie die Zahl nach dem Up hinzufügen.

#CLI variant
migrate create -ext sql -dir ./database/migrations -seq create_users_table
#Docker CLI variant
docker run --rm -v $(pwd)/database/migrations:/migrations migrate/migrate \
    create -ext sql -dir /migrations -seq create_users_table
  • Pfad: Pfad zum Migrationsverzeichnis.
  • Datenbank: Definieren Sie Ihre Datenbank-DSN-Verbindung.

HINWEIS: Wenn die Migration zum ersten Mal ausgeführt wird, wird eine Tabelle „schema_migrations“ erstellt, in der die Migration die angewendete Versionsnummer kennt.

Und führen Sie unsere Golang-Anwendung aus, um die Ergebnisse anzuzeigen:

How to use migrations with Golang

Neue Migration hinzufügen

Fügen Sie ein neues Spaltentelefon zur Benutzertabelle hinzu

CREATE TABLE `users` (
    `id` VARCHAR(36) NOT NULL PRIMARY KEY,
    `name` VARCHAR(255) NOT NULL,
    `email` VARCHAR(255) NOT NULL UNIQUE,
    `password` VARCHAR(255) NOT NULL
);
DROP TABLE IF EXISTS `users`;
#CLI variant
migrate -path=./database/migrations -database "mysql://root:root@tcp(localhost:3306)/app" up
#Docker CLI variant
docker run --rm -v $(pwd)/database/migrations:/migrations --network host migrate/migrate \
    -path=/migrations -database "mysql://root:root@tcp(localhost:3306)/app" up

Und wenn Sie es über unsere Golang-Anwendung ausführen, sehen Sie das neue Feld:

How to use migrations with Golang

So machen Sie die Migration rückgängig

Mit dem folgenden Befehl können wir die angewendeten Daten ganz einfach zurücksetzen. Migrationen. Im folgenden Beispiel können wir sehen, wie wir die zuletzt angewendete Migration rückgängig machen:

#CLI variant
migrate create -ext sql -dir ./database/migrations -seq add_column_phone

#Docker CLI variant
docker run --rm -v $(pwd)/database/migrations:/migrations migrate/migrate \
    create -ext sql -dir /migrations -seq add_column_phone
-- 000002_add_column_phone.up.sql
ALTER TABLE `users` ADD `phone` VARCHAR(255) NULL;

WARNUNG: Wenn Sie die Anzahl der Migrationen nicht definieren, wird ROLLBACK auf ALLE MIGRATIONEN angewendet!

Und dann können wir zeigen, dass die letzte Migration rückgängig gemacht und das Telefonfeld entfernt wurde :-)

Display table users without phone field

So beheben Sie Migrationsfehler

Wenn eine Migration Fehler enthält und ausgeführt wird, kann diese Migration nicht angewendet werden und das Migrationssystem verhindert weitere Migrationen in der Datenbank, bis diese Migration behoben ist.

Und wenn wir versuchen, uns zu bewerben, erhalten wir eine Nachricht wie diese:

services:
  mariadb:
    image: mariadb:11.5.2
    container_name: mariadb_example_go_migration
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE=app
      - MYSQL_ROOT_PASSWORD=root
      - TZ=Europe/Berlin
    volumes:
      - mariadbdata:/var/lib/mysql

volumes:
  mariadbdata:
    driver: local

docker compose up -d 

Keine Panik, es ist nicht schwer, zu einem einheitlichen System zurückzukehren.

Zuerst müssen wir die beschädigte Migration beheben, in diesem Fall Version 2.

Sobald die Migration gelöst ist, müssen wir das System auf die letzte gültige Version zwingen, in diesem Fall Version 1.

docker volume create -d local mariadbdata
docker run --name mariadb_example_go_migration -p 3306:3306 -e MYSQL_DATABASE=app -e MYSQL_ROOT_PASSWORD=root -e TZ=Europe/Berlin -v mariadbdata:/var/lib/mysql mariadb:11.5.2
DATABASE_DSN=root:root@tcp(localhost:3306)/app

Und jetzt können Sie die Migrationen problemlos erneut anwenden ;-)

Makefile

Um unsere Produktivität zu verbessern und die Verwendung dieser Befehle zu erleichtern, können wir Makefile verwenden. Unten sehen Sie die beiden Varianten: Native Client und Docker.

CLI-Variante

package main

import (
 "database/sql"
 "fmt"
 "log"
 "os"
 "text/tabwriter"

 _ "github.com/go-sql-driver/mysql"
 "github.com/joho/godotenv"
)

func main() {
 // Load .env variables
 err := godotenv.Load()
 if err != nil {
  log.Fatal("Error loading .env file")
 }

 // Open connection with MySQL DB
 db, err := sql.Open("mysql", os.Getenv("DATABASE_DSN"))
 if err != nil {
  log.Fatalf("Error opening database: %v\n", err)
 }
 defer db.Close()

 // Ensure that the connection works
 err = db.Ping()
 if err != nil {
  log.Fatalf("Error connecting database: %v\n", err)
 }

 fmt.Println("Connected to database")

 // Execute the SHOW TABLES query to list all tables in the database
 tables, err := db.Query("SHOW TABLES")
 if err != nil {
  log.Fatalf("Failed to execute SHOW TABLES query: %v\n", err)
 }
 defer tables.Close()

 fmt.Println("Database structure:")

 for tables.Next() {
  var tableName string
  if err := tables.Scan(&tableName); err != nil {
   log.Fatalf("Failed to scan table name: %v\n", err)
  }

  w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)

  fmt.Printf("\n[Table: %s]\n\n", tableName)
  fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n", "Field", "Type", "Null", "Key", "Default", "Extra")

  // Get the structure of the current table
  structureQuery := fmt.Sprintf("DESCRIBE %s", tableName)
  columns, err := db.Query(structureQuery)
  if err != nil {
   log.Fatalf("Failed to describe table %s: %v\n", tableName, err)
  }
  defer columns.Close()

  for columns.Next() {
   var field, colType, null, key, defaultVal, extra sql.NullString
   err := columns.Scan(&field, &colType, &null, &key, &defaultVal, &extra)
   if err != nil {
    log.Fatalf("Failed to scan column: %v\n", err)
   }

   fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n",
    field.String, colType.String, null.String, key.String, defaultVal.String, extra.String)
  }

  w.Flush()
 }
}

Docker-CLI-Variante

#CLI variant
migrate create -ext sql -dir ./database/migrations -seq create_users_table

Repository

Der Code für dieses Tutorial ist öffentlich zu finden: GitHub – albertcolom/example-go-migration


Original veröffentlicht unter: albertcolom.com

Das obige ist der detaillierte Inhalt vonSo verwenden Sie Migrationen mit Golang. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
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