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!
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.
Livros Recomendados
Abaixo listei alguns dos melhores livros que já li sobre GO.

