Se você não é muito fã de ORMs como eu, um dos problemas que você mais enfrenta é o de como realizar alterações em seu banco de dados de forma segura e automática.
Nesse post, vou mostrar como fazer isso utilizando o Migrate (https://github.com/golang-migrate/migrate), um projeto open source escrito em Go para realizar migrations em bancos de dados.
Antes de começar, embora os exemplos contidos nesse tutorial serem utilizando postgres, o Migrate suporta os seguintes bancos de dados:
mongodb+srv, firebirdsql, clickhouse, cockroachdb, mongodb, mysql, sqlserver, cassandra, crdb-postgres, postgres, postgresql, spanner, stub, cockroach, neo4j, pgx, redshift, firebird.
Tendo esclarecido esse ponto, vamos iniciar um projeto com nome github.com/aprendagolang/migrate (go mod init github.com/aprendagolang/migrate
) e depois escrever uma pequena API.
package main import ( "database/sql" "encoding/json" "net/http" "time" "github.com/aprendagolang/migrate/db" "github.com/go-chi/chi/v5" ) func main() { conn, err := db.OpenConnection() if err != nil { panic(err) } r := chi.NewRouter() r.Get("/users", ListUsers(conn)) http.ListenAndServe(":8080", r) } type User struct { ID sql.NullInt64 FirstName string LastName string CreatedAt time.Time } func ListUsers(conn *sql.DB) http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { rows, err := conn.Query(`SELECT * FROM users`) if err != nil { return } var users []User for rows.Next() { var user User err = rows.Scan(&user.ID, &user.FirstName, &user.LastName, &user.CreatedAt) if err != nil { continue } users = append(users, user) } rw.Header().Add("Content-Type", "application/json") json.NewEncoder(rw).Encode(users) } }
Como o intuito aqui é apenas exemplificar a utilização e automação de migrations, o código acima foi colocado todo no arquivo main.go.
Se você reparar, um dos imports que temos ali é github.com/aprendagolang/migrate/db, que é o nosso package de banco de dados. Dentro desse package, ou seja, dessa pasta, vamos criar um arquivo com o nome connection.go.
Nesse arquivo vamos escrever uma função para realizar a conexão com o banco de dados.
package db import ( "database/sql" _ "github.com/lib/pq" ) func OpenConnection() (*sql.DB, error) { conn, err := sql.Open("postgres", "host=localhost port=5432 user=gopher password=1122 dbname=foobar sslmode=disable") if err != nil { panic(err) } err = conn.Ping() if err != nil { panic(err) } return conn, err }
Isso nos dá uma API totalmente funcional. Porém ainda nos falta a parte principal, que é a automação das migrations.
Para isso, vou criar uma nova pasta com o nome de migrations. Essa pasta vai conter todos os arquivos de UP (fazer modificações) e DOWN (desfazer modificações).
Embora possamos criar e nomear esses arquivos na mão, para seguir a convenção do Migrate sem falhas, eu recomendo instalar o CLI na sua máquina (🛠 https://github.com/golang-migrate/migrate/tree/master/cmd/migrate).
Com o CLI instalado na sua máquina, e estando dentro da pasta do projeto, execute o seguinte comando:
$ migrate create -ext sql -dir migrations -seq users
Se você olhar na pasta migrations, verá que foram criados os arquivos 000001_users.up.sql e 000001_users.down.sql.
No arquivo 000001_users.up.sql, vamos adicionar o SQL para criar nossa tabela USERS.
CREATE TABLE IF NOT EXISTS users ( id BIGSERIAL primary key, first_name TEXT not null, last_name TEXT, created_at TIMESTAMP default now() );
E como o DOWN é responsável por reverter o UP, vamos adicionar um drop da tabela USERS no arquivo 000001_users.down.sql.
DROP TABLE IF EXISTS users;
Com os dois arquivos criados, e tendo o CLI instalado na máquina, já é possível aplicar a migration utilizando o comando:
$ migrate -path ./migrations -database "postgresql://gopher:1122@localhost:5432/foobar?sslmode=disable" -verbose up
E desfazer as mudanças com o comando:
$ migrate -path ./migrations -database "postgresql://gopher:1122@localhost:5432/foobar?sslmode=disable" -verbose down
Embora para realizar o DOWN de forma automatizada seja um pouco complexo, já que teríamos que ter alguma espécie de controle de versão do app, o UP é simples e como o Migrate controla as versões e só aplica o que há de novo, podemos aplicar o UP sempre que a app subir.
Para fazer isso, vamos fazer uma pequena modificação na nossa função que faz conexão com banco de dados.
Primeiramente vamos importar os packages:
"github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file"
Agora, logo após o teste de conectividade com a funçãoo Ping()
, vamos adicionar o código responsável por realizar o UP.
driver, err := postgres.WithInstance(conn, &postgres.Config{}) m, err := migrate.NewWithDatabaseInstance( "file://./migrations", "postgres", driver) if err != nil { panic(err) } m.Up()
Ao levantar nossa API com o comando go run main.go
, podemos ir até o banco de dados e verificar que a tabela users foi criada com sucesso.
Como eu disse anteriormente, para fazer o DOWN de forma automatizada depende muito do contexto de cada aplicação, e por isso não vou entrar nesse ponto. De qualquer forma, o código para fazer o DOWN seria basicamente o do exemplo anterior, apenas substituindo o m.Up()
por m.Down()
.
Deixem suas dúvidas nos comentários.
Até a próxima!