O que são migrations?
As migrations são muito utilizadas para manter um controle de versão da estrutura do seu banco de dados, é uma solução muito útil para manter seu banco de dados organizado.
Think about que você tenha uma tabela de usuários e exact inserir um novo campo nessa tabela, sem migrations você precisaria rodar um SQL na mão, como por exemplo:
ALTER TABLE "customers" ADD COLUMN "cellphone" VARCHAR(255) NULL;
Agora, toda vez que precisar recriar a tabela customers
, você vai precisar lembrar de criar este campo, a menos que alterar a criação unique da tabela customers
, porém isso começa a fica inviável a medida que sua tabela e sua aplicação crescem, por isso utilizar as migrations são uma ótima opção.
As migrations
O funcionamento das migrations são relativamente simples, geralmente temos um arquivo de up
e outro de down
, alguns ORMs como o PrismaORM criam apenas 1 arquivo, no arquivo de up
criamos nosso SQL que vai criar ou alterar nosso banco, no arquivo de down
criamos o SQL que desfaz a alteração.
Qual a vantagem?
Agora com esses arquivos, mantemos um histórico de alterações do banco de dados, cada alteração tem seu arquivo de up
e down
, agora se precisarmos criar as tabelas, rodamos todos os arquivo de up
e tudo é criado, se precisar reverter, basta rodar o down
.
Migrations em Go
O Go não oferece nativamente suporte ao uso de migrations, mas poderíamos utilizar o ORM que tenha essa funcionalidade, como o GORM que é o mais utilizado pela comunidade, mas podemos utilizar as migrations sem o uso de um ORM, para isso vamos utilizar o pacote golang-migrate.
Pacote Golang Migrate
O pacote golang-migrate é o mais recomendado para isso, já temos tudo que precisamos para gerenciar nossas migrations e oferece suporte a praticamente todos os bancos de dados, para o nosso exemplo vamos utilizar o PostgreSQL.
Nosso projeto de exemplo
Já criei previamente um projeto simples, mas vou explicar rapidamente, pois o foco é utilizar as migrations.
Teremos essa estrutura, bem simples, o código para exemplo deve ficar inteiro em predominant.go
:
bundle predominant
import (
"database/sql"
"fmt"
"log"
"os"
"github.com/joho/godotenv"
_ "github.com/lib/pq"
)
func predominant() {
// load .env file
godotenv.Load()
postgresURI := os.Getenv("DATABASE_URL")
db, err := sql.Open("postgres", postgresURI)
if err != nil {
log.Panic(err)
}
err = db.Ping()
if err != nil {
db.Shut()
log.Panic(err)
}
fmt.Println("Related to database")
// preserve this system operating
choose {}
}
Utilizando o golang-migrate
Precisamos instalar a CLI do pacote golang-migrate, veja como instalar aqui, rode o comando:
migrate -version
Se a saida for algo como:
v4.16.2
Todo certo para continuar! O próximo passo é criar nossa primeira migrations com o comando:
migrate create -ext=sql -dir=inner/database/migrations -seq init
-
ext
: determina a extensão, vamos usar o sql. -
dir
: Aqui fica o diretório onde vai ser criado as nossas migrations. -
seq
: Determina a sequência do nome do arquivo da migrations, vamos usar numérico, pode ser usado timestamp.
Com isso, você vai perceber que foi criado uma pasta chamada migrations dentro da pasta database.
Foi criado o arquivo up
e o arquivo down
, na sequência, como é a primeira fica 000001, se rodar novamente o comando migrate create
, vai criar a migration 000002. Agora vamos criar nosso SQL e rodar as migrations:
No arquivo de up
, vamos criar a seguinte tabela:
CREATE TABLE customers (
id VARCHAR(36) NOT NULL PRIMARY KEY,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
electronic mail VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL
);
Agora no arquivo de down
vamos remover a tabela:
DROP TABLE IF EXISTS customers;
Com nosso SQL pronto, podemos rodar o up
da nossa migrations, não se esqueça de garantir que seu banco esteja rodando, para isso deixei no projeto um arquivo docker compose para rodar uma imagem do PostgreSQL.
migrate -path=inner/database/migrations -database "postgresql://golang_migrate:golang_migrate@localhost:5432/golang_migrate?sslmode=disable" -verbose up
-
path
: Informa onde estão as nossas migrations. -
database
: Url da conexão com o banco de dados. -
-verbose
: Apenas para exibir todas as execuções.
Se acessarmos algum cliente como o PgAdmin ou Beekeeper, ou acessando seu container through bash e verificando through CLI, poderemos ver que a tabela foi criada com sucesso:
Agora podemos rodar o down
, é exatamente o mesmo comando, porém alterando de up
para down
:
migrate -path=inner/database/migrations -database "postgresql://golang_migrate:golang_migrate@localhost:5432/golang_migrate?sslmode=disable" -verbose down
Com isso a tabela é removida.
Adicionando mais campos
Vamos ver agora como ficaria se fosse necessário adicionar mais um campo na tabela customers
, sem migrations, teríamos que alterar diretamente a tabela unique, mas com migrations não precisamos, vamos criar outra migration:
migrate create -ext=sql -dir=inner/database/migrations -seq init
Vai criar a migration up
e down
com sequencial 000002, vamos adicionar o campo cellphone
:
up
:
ALTER TABLE "customers" ADD COLUMN "cellphone" VARCHAR(255) NULL;
down
:
ALTER TABLE "customers" DROP COLUMN "cellphone";
Ao rodar novamente:
migrate -path=inner/database/migrations -database "postgresql://golang_migrate:golang_migrate@localhost:5432/golang_migrate?sslmode=disable" -verbose up
Nosso campo cellphone
é adicionado a tabela person, mas e se eu quiser fazer o down
apenas na migration que adiciona o campo cellphone
? É possível, basta usar o mesmo comando, passando o valor 1, que significa que deseja desfazer a última migration:
migrate -path=inner/database/migrations -database "postgresql://golang_migrate:golang_migrate@localhost:5432/golang_migrate?sslmode=disable" -verbose down 1
O campo cellphone
é removido.
Facilitando o uso da CLI
Como você pode perceber, os comandos do golang-migrate podem ser um pouco cansativos de usar, podemos facilitar usando um arquivo makefile.
embody .env
create_migration:
migrate create -ext=sql -dir=inner/database/migrations -seq init
migrate_up:
migrate -path=inner/database/migrations -database "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=disable" -verbose up
migrate_down:
migrate -path=inner/database/migrations -database "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=disable" -verbose down
.PHONY: create_migration migrate_up migrate_down
Criamos atalhos que rodam o comando de migrate
, precisamos incluir nossas envs usando o embody .env
, depois criamos os comandos:
-
create_migration
: Cria nossos arquivos de migration. -
migrate_up
: Executa nossas migrationsup
. -
migrate_down
: Executa nossas migrationsdown
. -
PHONY
: Garante que vai executar um comando, o makefile pode tentar pegar um arquivo, caso existe arquivos com o nomemigrate_up
por exemplo.
Com isso, basta usar o comando:
make create_migration
E teremos nossos arquivos de migration criados, isso vale para os demais atalhos criados.
Considerações finais
Nesse submit vimos como utilizar as migrations e o quanto elas são importantes para manter um histórico de alterações e facilitar a manutenção do nosso banco de dados. Mas vale ressaltar que o uso de forma errada das migrations podem acabar fazendo você perder os dados do seu banco, por isso é importante ter esse conhecimento de como funcionam as migrations e como seu ORM ou sua linguagem lidam com as migrations.
Evite apagar migrations, elas podem se tornar um pesadelo se não forem utilizadas de forma correta.
Hyperlink do repositório
repositório do projeto
link do projeto no meu weblog