Exemple d'application simple montrant comment utiliser golang-migrate
Beaucoup de gens posent cette question et j'ai essayé de faire cette liste pour mettre en évidence les principaux avantages de l'utilisation des migrations :
Contrôle de version : L'un des principaux et des plus importants est de pouvoir avoir un versionnage des différentes modifications du schéma de la base de données. Sans les migrations, ces modifications de schéma seraient incohérentes et impossibles à suivre, ce qui entraînerait des problèmes de version et d'éventuelles erreurs.
Rollback : Il est toujours nécessaire d'avoir un système de rollback en cas de panne. Un système de migrations dispose toujours de deux méthodes :-)
Automation et intégration CI/CD : Les migrations peuvent être automatisées, leur permettant de faire partie du pipeline CI/CD. Cela aide à déployer les changements de manière fluide et cohérente sans intervention manuelle.
Nous pouvons trouver bien d'autres avantages mais je pense que ces points représentent un bon résumé des principaux avantages.
Go ne prend pas en charge les migrations de manière native pour cette raison, nous pouvons également utiliser le package populaire golang-migrate si vous utilisez un ORM comme GORM, vous pouvez l'utiliser pour cela.
Les deux packages sont très populaires mais dans cet exemple, j'utiliserai golang-migrate car je ne suis pas intéressé par l'implémentation d'un ORM.
Voyons étape par étape comment implémenter une application simple pour voir comment elle est utilisée.
Pour suivre cet article vous aurez besoin de : Go et Docker avec Docker Compose
Créez le fichier docker-compose.yml dans votre répertoire racine où nous définirons votre base de données préférée, dans mon cas utilisez une MariaDB mais n'hésitez pas à en utiliser une autre.
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
Si vous préférez, vous pouvez utiliser Docker directement au lieu de docker-compose :
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
Créez ou mettez à jour le fichier .env dans votre répertoire racine où vous devez définir les variables pour connecter notre base de données.
DATABASE_DSN=root:root@tcp(localhost:3306)/app
Créez une application Golang simple pour garantir une connexion réussie à la base de données et répertoriez toutes les tables et structures de la base de données avec leur structure. 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() } }
Et lorsque nous l'exécutons, nous obtenons un résultat similaire :
Pour exécuter golang-migrate CLI, vous disposez essentiellement de deux méthodes : installer la CLI localement ou exécuter via l'image Docker officielle : migrate/migrate.
Personnellement, je préfère la variante Docker mais dans ce tutoriel, illustrez les deux variantes.
La première étape consiste à créer une migration vide avec la commande suivante.
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
Cette commande générera deux fichiers vides sur le dossier database/migrations/ : 000001createuserstable.up.sql et 000001createuserstable.down.sql
Dans le fichier 000001createuserstable.up.sql, définissez SQL pour créer une table d'utilisateurs :
DATABASE_DSN=root:root@tcp(localhost:3306)/app
Dans le fichier 000001createuserstable.down.sql, définissez SQL pour annuler toutes les modifications apportées par up, dans ce cas nous devons supprimer la table des utilisateurs :
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() } }
La commande suivante applique toutes les migrations en attente. Vous pouvez également définir le nombre de migrations à appliquer en additionnant le nombre après le haut.
#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
REMARQUE : Lors de la première exécution de la migration une table "schema_migrations" sera créée dans laquelle la migration connaît le numéro de version appliqué.
Et lancez notre application Golang pour afficher les résultats :
Ajouter une nouvelle colonne de téléphone sur la table des utilisateurs
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
Et lorsque vous l'exécutez depuis notre application Golang, vous pouvez voir le nouveau champ :
Avec la commande suivante, nous pouvons facilement annuler le fichier appliqué. migrations. Dans l'exemple suivant, nous pouvons voir comment nous annulons la dernière migration appliquée :
#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;
ATTENTION : Si vous ne définissez pas le nombre de migrations, le ROLLBACK sera appliqué à TOUTES LES MIGRATIONS !
Et puis nous pouvons montrer que la dernière migration a été annulée et que le champ téléphone a été supprimé :-)
Si une migration contient des erreurs et est exécutée, cette migration ne peut pas être appliquée et le système de migration empêchera toute autre migration sur la base de données jusqu'à ce que cette migration soit corrigée.
Et lorsque nous essaierons de postuler, nous recevrons un message comme celui-ci :
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
Pas de panique, il n'est pas difficile de revenir à un système cohérent.
Nous devons d'abord résoudre la migration corrompue, dans ce cas la version 2.
Une fois la migration résolue, nous devons forcer le système à la dernière version valide, en l'occurrence la 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
Et maintenant vous pouvez réappliquer les migrations sans aucun problème ;-)
Pour améliorer notre productivité et faciliter l'utilisation de ces commandes nous pouvons utiliser Makefile. Ci-dessous vous pouvez voir les deux variantes : client natif et docker.
Variante CLI
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() } }
Variante Docker CLI
#CLI variant migrate create -ext sql -dir ./database/migrations -seq create_users_table
Le code de ce tutoriel peut être trouvé dans le public : GitHub - albertcolom/example-go-migration
Original publié sur : albertcolom.com
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!