1. Instalando a ferramenta ent

Para instalar a ferramenta de geração de código ent, você precisa seguir estes passos:

Primeiro, certifique-se de que o ambiente Go language esteja instalado no seu sistema. Em seguida, execute o seguinte comando para obter a ferramenta ent:

go get -d entgo.io/ent/cmd/ent

Este comando irá baixar o código da ferramenta ent, mas não o compilará e instalará imediatamente. Se deseja instalar o ent no diretório $GOPATH/bin para que possa usá-lo em qualquer lugar, também precisará executar o seguinte comando:

go install entgo.io/ent/cmd/ent

Após a conclusão da instalação, você pode verificar se a ferramenta ent está corretamente instalada e ver os comandos disponíveis e instruções executando ent -h.

2. Inicializando o Esquema

2.1 Inicializando o Modelo Usando ent init

Criar um novo arquivo de esquema é o primeiro passo para começar a usar o ent para a geração de código. Você pode inicializar o modelo de esquema executando o seguinte comando:

go run -mod=mod entgo.io/ent/cmd/ent new User Pet

Este comando criará dois novos arquivos de esquema: user.go e pet.go, e os colocará no diretório ent/schema. Se o diretório ent não existir, este comando também o criará automaticamente.

Executar o comando ent init no diretório raiz do projeto é uma boa prática, pois ajuda a manter a estrutura e clareza do diretório do projeto.

2.2 Estrutura do Arquivo de Esquema

No diretório ent/schema, cada esquema corresponde a um arquivo de origem da linguagem Go. Os arquivos de esquema são onde você define o modelo do banco de dados, incluindo os campos e arestas (relacionamentos).

Por exemplo, no arquivo user.go, você pode definir um modelo de usuário, incluindo campos como nome de usuário e idade, e definir o relacionamento entre usuários e pets. Da mesma forma, no arquivo pet.go, você definiria o modelo de pet e seus campos relacionados, como nome do pet, tipo e o relacionamento entre pets e usuários.

Esses arquivos serão usados pelo ent para gerar código Go correspondente, incluindo código cliente para operações de banco de dados e operações CRUD (Create, Read, Update, Delete).

// ent/schema/user.go
package schema

import (
    "entgo.io/ent"
    "entgo.io/ent/schema/field"
)

// User define o esquema para a entidade de usuário.
type User struct {
    ent.Schema
}

// O método Fields é usado para definir os campos da entidade.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").Unique(),
        field.Int("age").Positive(),
    }
}

// O método Edges é usado para definir as associações da entidade.
func (User) Edges() []ent.Edge {
    // As associações serão explicadas em mais detalhes na próxima seção.
}

Os arquivos de esquema ent utilizam tipos e funções da linguagem Go para declarar a estrutura do modelo do banco de dados, incluindo os campos e relacionamentos entre modelos, e utilizam a API fornecida pelo framework ent para definir essas estruturas. Essa abordagem torna o ent muito intuitivo e fácil de estender, enquanto aproveita a digitação forte da linguagem Go.

3.1 Executando a Geração de Código

Executar ent generate para gerar código é um passo crucial no framework ent. Com este comando, o ent irá gerar arquivos de código Go correspondentes com base nos esquemas definidos, facilitando o trabalho de desenvolvimento subsequente. O comando para a execução da geração de código é direto:

go generate ./ent

O comando acima precisa ser executado no diretório raiz do projeto. Quando o go generate é chamado, ele buscará todos os arquivos Go contendo anotações específicas e executará os comandos especificados nas anotações. Em nosso exemplo, este comando especifica o gerador de código para o ent.

Certifique-se de que a inicialização do esquema e as adições de campo necessárias tenham sido concluídas antes da execução. Somente então o código gerado incluirá todas as partes necessárias.

3.2 Compreensão dos Ativos de Código Gerado

Os ativos de código gerado contêm vários componentes, cada um com funções diferentes:

  • Objetos Client e Tx: Usados para interagir com o gráfico de dados. O Cliente fornece métodos para criar transações (Tx) ou executar operações diretamente no banco de dados.

  • Builders CRUD: Para cada tipo de esquema, gera builders para criar, ler, atualizar e excluir, simplificando a lógica de operação da entidade correspondente.

  • Objeto de entidade (struct em Go): Gera as structs em Go correspondentes para cada tipo no esquema, mapeando essas structs para as tabelas no banco de dados.

  • Pacote de constantes e predicados: Contém constantes e predicados para interagir com os builders.

  • Pacote de migração: Um pacote para migração de banco de dados, adequado para dialetos SQL.

  • Pacote de hook: Fornece a funcionalidade para adicionar middleware de alteração, permitindo que lógica personalizada seja executada antes ou depois de criar, atualizar ou excluir entidades.

Ao examinar o código gerado, você pode obter uma compreensão mais profunda de como o framework ent automatiza o código de acesso aos dados para seus esquemas.

4. Opções de Geração de Código

O comando ent generate suporta várias opções para personalizar o processo de geração de código. Você pode consultar todas as opções de geração suportadas através do seguinte comando:

ent generate -h

Aqui estão algumas opções de linha de comando comumente utilizadas:

  • --feature strings: Estende a geração de código, adicionando funcionalidades extras.
  • --header string: Substitui o arquivo de cabeçalho de geração de código.
  • --storage string: Especifica o driver de armazenamento suportado na geração de código, com padrão para "sql".
  • --target string: Especifica o diretório de destino para a geração de código.
  • --template strings: Executa modelos adicionais em Go. Suporta caminho de arquivo, diretório e curinga, por exemplo: --template file="caminho/para/arquivo.tmpl".

Essas opções permitem que os desenvolvedores personalizem seu processo de geração de código de acordo com diferentes necessidades e preferências.

5. Configuração da Opção de Armazenamento

O ent suporta a geração de ativos de código tanto para dialetos SQL quanto para Gremlin, sendo o padrão o dialeto SQL. Se o projeto precisar se conectar a um banco de dados Gremlin, o dialeto correspondente precisa ser configurado. O seguinte demonstra como especificar as opções de armazenamento:

ent generate --storage gremlin ./ent/schema

O comando acima instrui o ent a usar o dialeto Gremlin ao gerar o código. Isso garante que os ativos gerados sejam adaptados aos requisitos do banco de dados Gremlin, garantindo compatibilidade com um banco de dados de grafo específico.

6. Uso Avançado: Pacote entc

6.1 Uso do entc como Pacote no Projeto

O entc é o pacote principal usado para a geração de código no framework ent. Além da ferramenta de linha de comando, o entc também pode ser integrado ao projeto como um pacote, permitindo que os desenvolvedores controlem e personalizem o processo de geração de código dentro do próprio código.

Para usar o entc como um pacote no projeto, você precisa criar um arquivo chamado entc.go e adicionar o seguinte conteúdo ao arquivo:

// +build ignore

package main

import (
    "log"
    "entgo.io/ent/entc"
    "entgo.io/ent/entc/gen"
)

func main() {
    if err := entc.Generate("./schema", &gen.Config{}); err != nil {
        log.Fatal("executando a geração de código do ent:", err)
    }
}

Ao utilizar essa abordagem, você pode modificar a estrutura gen.Config dentro da função main para aplicar diferentes opções de configuração. Chamando a função entc.Generate conforme necessário, você pode controlar de forma flexível o processo de geração de código.

6.2 Configuração Detalhada do entc

entc fornece extensas opções de configuração, permitindo que os desenvolvedores personalizem o código gerado. Por exemplo, ganchos personalizados podem ser configurados para inspecionar ou modificar o código gerado, e dependências externas podem ser injetadas usando injeção de dependência.

O exemplo a seguir demonstra como fornecer ganchos personalizados para a função entc.Generate:

func main() {
    err := entc.Generate("./esquema", &gen.Config{
        Hooks: []gen.Hook{
            HookFunction,
        },
    })
    if err != nil {
        log.Fatalf("executando a geração de código ent: %v", err)
    }
}

// HookFunction é uma função de gancho personalizada
func HookFunction(next gen.Generator) gen.Generator {
    return gen.GenerateFunc(func(g *gen.Graph) error {
        // Pode manipular o modo de gráfico representado por g aqui
        // Por exemplo, validar a existência de campos ou modificar a estrutura
        return next.Generate(g)
    })
}

Além disso, as dependências externas podem ser adicionadas usando entc.Dependency:

func main() {
    opts := []entc.Option{
        entc.Dependency(
            entc.DependencyType(&http.Client{}),
        ),
        entc.Dependency(
            entc.DependencyName("Writer"),
            entc.DependencyTypeInfo(&field.TypeInfo{
                Ident:   "io.Writer",
                PkgPath: "io",
            }),
        ),
    }
    if err := entc.Generate("./esquema", &gen.Config{}, opts...); err != nil {
        log.Fatalf("executando a geração de código ent: %v", err)
    }
}

Neste exemplo, injectamos http.Client e io.Writer como dependências nos objetos de cliente gerados.

7. Saída da Descrição do Esquema

No framework ent, o comando ent describe pode ser usado para gerar a descrição do esquema em um formato gráfico. Isso pode ajudar os desenvolvedores a entender rapidamente as entidades e relacionamentos existentes.

Execute o seguinte comando para obter a descrição do esquema:

go run -mod=mod entgo.io/ent/cmd/ent describe ./ent/esquema

O comando acima irá gerar uma tabela semelhante à seguinte, exibindo informações como campos, tipos, relacionamentos, etc. para cada entidade:

Usuário:
    +-------+---------+--------+-----------+ ...
    | Campo |  Tipo   | Único  | Opcional  | ...
    +-------+---------+--------+-----------+ ...
    | id    | int     | false  | false     | ...
    | nome  | string  | true   | false     | ...
    +-------+---------+--------+-----------+ ...
    +-------+--------+---------+-----------+ ...
    | Aresta|  Tipo   | Inverso | Relação   | ...
    +-------+--------+---------+-----------+ ...
    | pets  | Pet     | false   | O2M       | ...
    +-------+--------+---------+-----------+ ...

8. Ganchos de Geração de Código

8.1 Conceito de Ganchos

Ganchos são funções middleware que podem ser inseridas no processo de geração de código ent, permitindo a inserção de lógica personalizada antes e depois da geração de código. Os ganchos podem ser usados para manipular a árvore de sintaxe abstrata (AST) do código gerado, realizar validação ou adicionar trechos de código adicionais.

8.2 Exemplo de Uso de Ganchos

Aqui está um exemplo de uso de um gancho para garantir que todos os campos contenham uma determinada tag de estrutura (por exemplo, json):

func main() {
    err := entc.Generate("./esquema", &gen.Config{
        Hooks: []gen.Hook{
            EnsureStructTag("json"),
        },
    })
    if err != nil {
        log.Fatalf("executando a geração de código ent: %v", err)
    }
}

// EnsureStructTag garante que todos os campos no gráfico contenham uma tag de estrutura específica
func EnsureStructTag(name string) gen.Hook {
    return func(next gen.Generator) gen.Generator {
        return gen.GenerateFunc(func(g *gen.Graph) error {
            for _, node := range g.Nodes {
                for _, field := range node.Fields {
                    tag := reflect.StructTag(field.StructTag)
                    if _, ok := tag.Lookup(name); !ok {
                        return fmt.Errorf("a tag de estrutura %q está ausente para o campo %s.%s", name, node.Name, field.Name)
                    }
                }
            }
            return next.Generate(g)
        })
    }
}

Neste exemplo, antes de gerar o código, a função EnsureStructTag verifica cada campo para a tag json. Se um campo estiver faltando essa tag, a geração de código será interrompida e retornará um erro. Esta é uma maneira eficaz de manter a limpeza e consistência do código.