首頁  >  文章  >  後端開發  >  如何透過 Golang 使用遷移

如何透過 Golang 使用遷移

Linda Hamilton
Linda Hamilton原創
2024-11-09 07:28:02852瀏覽

簡單的範例應用程式展示如何使用 golang-migrate

為什麼應該使用遷移?

很多人問這個問題,我試著列出這個清單來強調使用遷移的主要優點:

版本控制:主要也是最重要的之一是能夠對資料庫模式的不同修改進行版本控制。如果沒有遷移,這些架構變​​更將是不連貫的並且無法跟踪,這將導致版本控制問題和可能的錯誤。

回滾:總是需要有一個回滾系統,以防出現任何故障。遷移系統總是有兩種方法,向上應用資料庫中的更改,向下負責快速一致地恢復更改:-)

自動化和 CI/CD 整合: 遷移可以自動化,使其成為 CI/CD 管道的一部分。這有助於順利、一致地部署更改,無需手動幹預。

我們可以找到更多優點,但我認為這些點很好地總結了主要優點。

如何在Golang中實現遷移?

Go 本身不支援該功能的遷移,因此我們可以使用流行的 golang-migrate 包,如果您使用像 GORM 這樣的 ORM,您也可以使用它。

這兩個套件都很受歡迎,但在這個例子中我將使用 golang-migrate,因為我對實現 ORM 不感興趣。

顯示代碼!

讓我們一步一步來實現一個簡單的應用程序,看看它是如何使用的。

要閱讀本文,您需要:Go 和 Docker 以及 Docker Compose

基礎設施

在根目錄中建立檔案 docker-compose.yml,我們將在其中定義您最喜歡的資料庫,在我的範例中使用 MariaDB,但也可以隨意使用另一個資料庫。

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 而不是 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

環境價值觀

在根目錄中建立或更新檔案 .env,您需要在其中定義變數以連接我們的資料庫。

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

創建一個簡單的 golang 應用程式

建立一個簡單的golang應用程式以確保成功的資料庫連接並列出資料庫中的所有表和結構及其結構。 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()
 }
}

當我們運行它時,我們有類似的輸出:

How to use migrations with Golang

遷移 CLI

要執行 golang-migrate CLI 基本上有兩種方法在本地安裝 CLI 或透過官方 Docker 映像運行:migrate/migrate。

就我個人而言,我更喜歡 de docker 變體,但在本教程中說明了這兩種變體。

如何產生遷移

第一步是使用下一個指令建立一個空遷移。

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:要產生的檔案的副檔名。
  • dir:將在其中建立遷移的目錄。
  • seq:遷移序列名稱。

此指令將在database/migrations/資料夾中產生兩個空檔:000001createuserstable.up.sql和000001createuserstable.down.sql

在 000001createuserstable.up.sql 檔案中定義建立表格 users 的 SQL:

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

在 000001createuserstable.down.sql 檔案中定義 SQL 來恢復 up 所做的所有更改,在這種情況下我們必須刪除 users 表:

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()
 }
}

如何申請移民

以下指令套用所有待處理的遷移。您也可以透過在 up 後新增數字來定義要套用的遷移數量。

#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
  • 路徑:遷移目錄的路徑。
  • 資料庫:定義資料庫 DSN 連線。

注意:第一次運行遷移時,將建立一個表“schema_migrations”,其中遷移知道所應用的版本號。

並執行我們的 Golang 應用程式來顯示結果:

How to use migrations with Golang

新增遷移

在使用者表上新增新的電話列

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

當您從我們的 Golang 應用程式運行它時,您可以看到新欄位:

How to use migrations with Golang

如何恢復遷移

透過以下指令我們可以輕鬆回滾已套用的。遷徙。在下面的範例中,我們可以看到如何反轉上次應用的遷移:

#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;

警告:如果您沒有定義遷移數量,ROLLBACK將應用於所有遷移

然後我們可以顯示上次遷移已恢復並且電話欄位已刪除:-)

Display table users without phone field

如何解決遷移錯誤

如果遷移包含錯誤並被執行,則無法套用該遷移,且遷移系統將阻止資料庫上的任何進一步遷移,直到此遷移已修復。

嘗試申請時,我們會收到以下訊息:

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 

不要驚慌,恢復一致的系統並不難。

首先,我們必須解決損壞的遷移問題,在本例中為版本 2。

遷移解決後,我們必須強制系統使用最新的有效版本,在本例中為版本 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

現在您可以毫無問題地重新套用遷移;-)

產生檔案

為了提高我們的工作效率並方便使用這些命令,我​​們可以使用 Makefile。下面您可以看到兩個變體:本機客戶端和 docker。

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()
 }
}

Docker CLI 變體

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

儲存庫

本教學的程式碼可以在公共場合找到:GitHub - albertcolom/example-go-migration


原文發表於:albertcolom.com

以上是如何透過 Golang 使用遷移的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn