用 Golang 處理資料庫遷移
最近在r/reddit
中不斷出現
我如何使用 Go 來完成資料庫遷移?
對於我和大多數人這種從其他語言例如 PHP 或是 Ruby 轉到 Go 的人來說,資料庫遷移在這些語言上已經不是什麼問題了。例如 Ruby 的 Rails 和 PHP 的 Laravel。但我如何在 Go 中複製這種功能呢?同時考慮到框架是 Go 中的反模式這一事實。
舉個例子,在在 Rails 和 Laravel 中可以非常輕鬆的使用bin/rails db:migrate
或者php artisan migrate
命令作為部署流水線的一個步驟來執行。但是同樣的功能如何在 Go 應用中實現呢?
已經有許多的庫被建立來解決 Go 的這一問題 , 但是目前來說migrate library 是我使用效果最好的一個庫。接下來我將會構建一個只有 package main 的小程式來展示你怎麼能構建任何一個 Go Web 程式在它啟動的時候來進行自動資料庫遷移,以及你如何能解決一些部署上的疑難雜症。接下來我將會解釋它在實際 中如何實現。
一個簡單應用
根據每個遷移檔案來說,migrate
庫都需要一些規則。遷移檔案必須命名為1_create_XXX.up.sql
和1 _create_xxx.down.sql
。所以基本上,每個遷移都應該有一個up.sql
和一個down.sql
檔案。在實際執行遷移時將執行up.sql
檔案,而在嘗試回滾時將執行down.sql
檔案。
不過你也可以使用migrate
cli 工具來建立遷移 :migrate create -ext sql create_users_table
package main import ( "database/sql" "flag" "fmt" "log" "os" _ "github.com/go-sql-driver/mysql" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/mysql" _ "github.com/golang-migrate/migrate/v4/source/file" ) func main() { var migrationDir = flag.String("migration.files", "./migrations", "Directory where the migration files are located ?") var MySQLDSN = flag.String("mysql.dsn", os.Getenv("MYSQL_DSN"), "Mysql DSN") flag.Parse() db, err := sql.Open("mysql", *mysqlDSN) if err != nil { log.Fatalf("could not connect to PostgreSQL database... %v", err) } if err := db.Ping(); err != nil { log.Fatalf("could not ping DB... %v", err) } // Run migrations // 開始資料遷移 driver, err := MySQL.WithInstance(db, &mysql.Config{}) if err != nil { log.Fatalf("could not start sql migration... %v", err) } m, err := migrate.NewWithDatabaseInstance( fmt.Sprintf("file://%s", *migrationDir), // file://path/to/directory "mysql", driver) if err != nil { log.Fatalf("migration failed... %v", err) } if err := m.Up(); err != nil && err != migrate.ErrNoChange { log.Fatalf("An error occurred while syncing the database.. %v", err) } log.Println("Database migrated") // actual logic to start your application os.Exit(0) }
以上是在 Go 中進行資料庫遷移的最簡單方法。你可以繼續從這個 repo 下載以下檔案,並將它們放在 migrations 目錄中或你認為合適的任何位置。之後,你需要使用以下命令執行它 :
$ go run main.go -mysql.dsn "root:@tcp(localhost)/xyz"
如果一切順利,你應該看到在標準輸出上列印了 "Database migrated" ( 資料庫遷移完成 )。
實際部署考慮事項
雖然這非常容易設定,但是它確實對檔案系統產生了依賴性——為了使遷移成為可能,必須提供遷移檔案。這也很容易解決。有三種方法可以解決這個問題:
- 如果你的應用程式在容器中執行,只需將遷移檔案掛載到映像中即可。下面是一個例子:
FROM Golang:1.11 as build-env WORKDIR /go/src/github.com/adelowo/project ADD . /go/src/github.com/adelowo/project ENV GO111MODULE=on RUN go mod download RUN go mod verify RUN go install ./cmd ## A better scratch FROM gcr.io/distroless/base COPY --from=build-env /go/bin/cmd / COPY --from=build-env /go/src/github.com/adelowo/project/path/to/migrations /migrations CMD ["/cmd"]
-
如果你已經有了 CI/CD 流程,那麼你可以使用
migrate
附帶的 cli 工具。只要在實際部署過程之前包含它就可以了,當在自動化測試階段你獲取了檔案的原始碼——那麼理想情況下,至少它們是被標識版本的。詳情請參考文件 。
雖然我還沒實踐過,但是以上方法確實是可行的方案
最後一步實際上是正在進行的工作。但是它依賴於將遷移檔案嵌入到二進位制檔案。通過這一步,檔案系統的依賴性將被破壞。現在有一個open Pull-Requests ,並且當它的狀態發生改變的時候,我將會持續留意,並更新這篇文章。