1. Visão Geral do Mecanismo de Migração

1.1 Conceito e Papel da Migração

A migração de banco de dados é o processo de sincronização de alterações nos modelos de dados com a estrutura do banco de dados, o que é crucial para a persistência de dados. À medida que a versão do aplicativo itera, o modelo de dados frequentemente passa por alterações, como adição ou exclusão de campos, ou modificação de índices. A migração permite que os desenvolvedores gerenciem essas alterações de forma versionada e sistemática, garantindo a consistência entre a estrutura do banco de dados e o modelo de dados.

No desenvolvimento web moderno, o mecanismo de migração oferece os seguintes benefícios:

  1. Controle de Versão: Os arquivos de migração podem rastrear o histórico de alterações na estrutura do banco de dados, facilitando o rollback e compreensão das alterações em cada versão.
  2. Implantação Automatizada: Através do mecanismo de migração, a implantação e atualizações do banco de dados podem ser automatizadas, reduzindo a possibilidade de intervenção manual e o risco de erros.
  3. Colaboração em Equipe: Os arquivos de migração garantem que os membros da equipe usem estruturas de banco de dados sincronizadas em diferentes ambientes de desenvolvimento, facilitando o desenvolvimento colaborativo.

1.2 Recursos de Migração do Framework ent

A integração do framework ent com o mecanismo de migração oferece os seguintes recursos:

  1. Programação Declarativa: Os desenvolvedores só precisam se concentrar na representação em Go das entidades, e o framework ent lidará com a conversão das entidades para tabelas de banco de dados.
  2. Migração Automática: O ent pode criar e atualizar automaticamente as estruturas das tabelas do banco de dados sem a necessidade de escrever manualmente instruções DDL.
  3. Controle Flexível: O ent fornece várias opções de configuração para suportar diferentes requisitos de migração, como com ou sem restrições de chave estrangeira, e geração de IDs globalmente únicos.

2. Introdução à Migração Automática

2.1 Princípios Básicos da Migração Automática

O recurso de migração automática do framework ent é baseado nos arquivos de definição de esquema (geralmente encontrados no diretório ent/schema) para gerar a estrutura do banco de dados. Após os desenvolvedores definirem entidades e relacionamentos, o ent inspecionará a estrutura existente no banco de dados e gerará operações correspondentes para criar tabelas, adicionar ou modificar colunas, criar índices, etc.

Além disso, o princípio de migração automática do ent funciona em um "modo de adição": por padrão, ele apenas adiciona novas tabelas, novos índices ou colunas às tabelas, e não exclui tabelas ou colunas existentes. Este design é benéfico para evitar perda acidental de dados e facilita a expansão da estrutura do banco de dados de maneira progressiva.

2.2 Utilizando Migração Automática

Os passos básicos para utilizar a migração automática do ent são os seguintes:

package main

import (
    "context"
    "log"
    "ent"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("Falha ao conectar ao MySQL: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // Realiza a migração automática para criar ou atualizar o esquema do banco de dados
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("Falha ao criar o esquema do banco de dados: %v", err)
    }
}

No código acima, ent.Open é responsável por estabelecer uma conexão com o banco de dados e retornar uma instância do cliente, enquanto client.Schema.Create executa a operação de migração automática real.

3. Aplicações Avançadas da Migração Automática

3.1 Excluir Colunas e Índices

Em alguns casos, pode ser necessário remover colunas ou índices que não são mais necessários no esquema do banco de dados. Nesse momento, podemos usar as opções WithDropColumn e WithDropIndex. Por exemplo:

// Executar migração com opções para excluir colunas e índices.
err = client.Schema.Create(
    ctx,
    migrate.WithDropIndex(true),
    migrate.WithDropColumn(true),
)

Este trecho de código habilita a configuração para excluir colunas e índices durante a migração automática. O ent excluirá quaisquer colunas e índices que não existam na definição do esquema ao executar a migração.

3.2 ID Global Único

Por padrão, as chaves primárias em bancos de dados SQL começam a partir de 1 para cada tabela, e diferentes tipos de entidades podem compartilhar o mesmo ID. Em alguns cenários de aplicação, como ao usar o GraphQL, pode ser necessário fornecer a singularidade global para os IDs de objetos de diferentes tipos de entidade. No ent, isso pode ser configurado usando a opção WithGlobalUniqueID:

// Executar migração com IDs universalmente únicos para cada entidade.
if err := client.Schema.Create(ctx, migrate.WithGlobalUniqueID(true)); err != nil {
    log.Fatalf("Falha ao criar o esquema do banco de dados: %v", err)
}

Após habilitar a opção WithGlobalUniqueID, o ent atribuirá uma faixa de ID de 2^32 a cada entidade em uma tabela chamada ent_types para alcançar a singularidade global.

3.3 Modo Offline

O modo offline permite escrever alterações de esquema para um io.Writer em vez de executá-las diretamente no banco de dados. É útil para verificar os comandos SQL antes que as alterações tenham efeito, ou para gerar um script SQL para execução manual. Por exemplo:

// Despejar as alterações da migração para um arquivo
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("Falha ao criar arquivo de migração: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("Falha ao imprimir as alterações no esquema do banco de dados: %v", err)
}

Este código irá escrever as alterações da migração para um arquivo chamado migrate.sql. Na prática, os desenvolvedores podem escolher imprimir diretamente na saída padrão ou escrever em um arquivo para revisão ou manter registro.

4. Suporte a Chave Estrangeira e Ganchos Personalizados

4.1 Ativar ou Desativar Chaves Estrangeiras

No Ent, as chaves estrangeiras são implementadas definindo relacionamentos (edges) entre entidades, e esses relacionamentos de chave estrangeira são criados automaticamente no nível do banco de dados para garantir integridade e consistência dos dados. No entanto, em certas situações, como para otimização de desempenho ou quando o banco de dados não suporta chaves estrangeiras, você pode optar por desativá-las.

Para ativar ou desativar as restrições de chave estrangeira nas migrações, você pode controlar isso através da opção de configuração WithForeignKeys:

// Ativar chaves estrangeiras
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true), 
)
if err != nil {
    log.Fatalf("Falha ao criar recursos de esquema com chaves estrangeiras: %v", err)
}

// Desativar chaves estrangeiras
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false), 
)
if err != nil {
    log.Fatalf("Falha ao criar recursos de esquema sem chaves estrangeiras: %v", err)
}

Esta opção de configuração precisa ser passada ao chamar Schema.Create, e ela determina se incluir restrições de chave estrangeira no DDL gerado com base no valor especificado.

4.2 Aplicação de Ganchos de Migração

Ganchos de migração são lógicas personalizadas que podem ser inseridas e executadas em diferentes estágios da execução da migração. Eles são muito úteis para realizar lógica específica no banco de dados antes/depois da migração, como validar os resultados da migração e pré-preencher dados.

Aqui está um exemplo de como implementar ganchos de migração personalizados:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // Código personalizado a ser executado antes da migração
        // Por exemplo, registro, verificação de certas precondições, etc.
        log.Println("Lógica personalizada antes da migração")
        
        // Chamar o próximo gancho ou a lógica padrão de migração
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }
        
        // Código personalizado a ser executado após a migração
        // Por exemplo, limpeza, migração de dados, verificações de segurança, etc.
        log.Println("Lógica personalizada após a migração")
        return nil
    })
}

// Usando ganchos personalizados na migração
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("Erro ao aplicar ganchos de migração personalizados: %v", err)
}

Os ganchos são ferramentas poderosas e indispensáveis para migrações complexas, dando a você a capacidade de controlar diretamente o comportamento da migração do banco de dados quando necessário.

5. Migrações Versionadas

5.1 Introdução às Migrações Versionadas

Migração versionada é um padrão para gerenciar a migração de banco de dados, permitindo que os desenvolvedores dividam as alterações na estrutura do banco de dados em várias versões, cada uma contendo um conjunto específico de comandos de modificação do banco de dados. Comparado à Migração Automática, a migração versionada fornece um controle mais refinado, garantindo rastreabilidade e reversibilidade das alterações na estrutura do banco de dados.

A principal vantagem da migração versionada é o suporte para migração avançada e reversa (ou seja, atualização ou rollback), permitindo que os desenvolvedores apliquem, revertam ou pulem alterações específicas conforme necessário. Ao colaborar em equipe, a migração versionada garante que cada membro trabalhe na mesma estrutura de banco de dados, reduzindo problemas causados por inconsistências.

A migração automática é frequentemente irreversível, gerando e executando instruções SQL para corresponder ao estado mais recente dos modelos de entidade, usada principalmente em estágios de desenvolvimento ou em projetos pequenos.

5.2 Utilizando Migrações Versionadas

1. Instalando a Ferramenta Atlas

Antes de usar as migrações versionadas, é necessário instalar a ferramenta Atlas em seu sistema. O Atlas é uma ferramenta de migração que suporta múltiplos sistemas de banco de dados, fornecendo recursos poderosos para gerenciar alterações no esquema do banco de dados.

macOS + Linux

curl -sSf https://atlasgo.sh | sh

Homebrew

brew install ariga/tap/atlas

Docker

docker pull arigaio/atlas
docker run --rm arigaio/atlas --help

Windows

https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe

2. Gerando Arquivos de Migração com Base nas Definições de Entidade Atuais

atlas migrate diff nome_da_migracao \
  --dir "file://ent/migrate/migrations" \
  --to "ent://ent/schema" \
  --dev-url "docker://mysql/8/ent"

3. Arquivos de Migração da Aplicação

Depois que os arquivos de migração são gerados, eles podem ser aplicados nos ambientes de desenvolvimento, teste ou produção. Normalmente, você primeiro aplicaria esses arquivos de migração a um banco de dados de desenvolvimento ou teste para garantir que eles sejam executados como esperado. Em seguida, os mesmos passos de migração seriam executados no ambiente de produção.

atlas migrate apply \
  --dir "file://ent/migrate/migrations" \
  --url "mysql://root:pass@localhost:3306/example"

Use o comando atlas migrate apply, especificando o diretório dos arquivos de migração (--dir) e o URL do banco de dados de destino (--url) para aplicar os arquivos de migração.