1. Introdução ao ent
Ent é um framework de entidades desenvolvido pelo Facebook especificamente para a linguagem Go. Ele simplifica o processo de construção e manutenção de aplicativos de modelo de dados em grande escala. O framework ent segue principalmente os seguintes princípios:
- Modelar facilmente o esquema do banco de dados como uma estrutura de gráfico.
- Definir o esquema na forma de código da linguagem Go.
- Implementar tipos estáticos com base na geração de código.
- Escrever consultas de banco de dados e travessia de gráficos é muito simples.
- Fácil de estender e personalizar usando modelos Go.
2. Configuração do Ambiente
Para começar a usar o framework ent, verifique se a linguagem Go está instalada no seu ambiente de desenvolvimento.
Se o diretório do seu projeto estiver fora do GOPATH
, ou se você não estiver familiarizado com o GOPATH
, você pode usar o seguinte comando para criar um novo projeto de módulo Go:
go mod init entdemo
Isso inicializará um novo módulo Go e criará um novo arquivo go.mod
para o seu projeto entdemo
.
3. Definindo o Primeiro Esquema
3.1. Criando Esquema Usando a ent CLI
Primeiro, você precisa executar o seguinte comando no diretório raiz do seu projeto para criar um esquema chamado User usando a ferramenta ent CLI:
go run -mod=mod entgo.io/ent/cmd/ent new User
O comando acima irá gerar o esquema User no diretório entdemo/ent/schema/
:
Arquivo entdemo/ent/schema/user.go
:
package schema
import "entgo.io/ent"
// User mantém a definição de esquema para a entidade User.
type User struct {
ent.Schema
}
// Campos do User.
func (User) Fields() []ent.Field {
return nil
}
// Arestas do User.
func (User) Edges() []ent.Edge {
return nil
}
3.2. Adicionando Campos
Em seguida, precisamos adicionar definições de campo ao Esquema do Usuário. Abaixo está um exemplo de adição de dois campos à entidade User.
Arquivo modificado entdemo/ent/schema/user.go
:
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// Campos do User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age").
Positive(),
field.String("name").
Default("unknown"),
}
}
Este trecho de código define dois campos para o modelo User: age
e name
, onde age
é um número inteiro positivo e name
é uma string com um valor padrão de "unknown".
3.3. Gerando Entidades de Banco de Dados
Após definir o esquema, você precisa executar o comando go generate
para gerar a lógica de acesso ao banco de dados subjacente.
Execute o seguinte comando no diretório raiz do seu projeto:
go generate ./ent
Este comando gerará o código Go correspondente com base no esquema previamente definido, resultando na seguinte estrutura de arquivos:
ent
├── client.go
├── config.go
├── context.go
├── ent.go
├── generate.go
├── mutation.go
... (vários arquivos omitidos por brevidade)
├── schema
│ └── user.go
├── tx.go
├── user
│ ├── user.go
│ └── where.go
├── user.go
├── user_create.go
├── user_delete.go
├── user_query.go
└── user_update.go
4.1. Inicializando a Conexão com o Banco de Dados
Para estabelecer uma conexão com o banco de dados MySQL, podemos usar a função Open
fornecida pelo framework ent
. Primeiro, importe o driver do MySQL e forneça a string de conexão correta para inicializar a conexão com o banco de dados.
package main
import (
"context"
"log"
"entdemo/ent"
_ "github.com/go-sql-driver/mysql" // Importe o driver do MySQL
)
func main() {
// Use ent.Open para estabelecer uma conexão com o banco de dados MySQL.
// Lembre-se de substituir os espaços reservados "seu_nome_de_usuário", "sua_senha" e "seu_banco_de_dados" abaixo.
cliente, err := ent.Open("mysql", "seu_nome_de_usuário:sua_senha@tcp(localhost:3306)/seu_banco_de_dados?parseTime=True")
if err != nil {
log.Fatalf("falha ao abrir conexão com o mysql: %v", err)
}
defer cliente.Close()
// Execute a ferramenta de migração automática
ctx := context.Background()
if err := cliente.Schema.Create(ctx); err != nil {
log.Fatalf("falha ao criar recursos de esquema: %v", err)
}
// Lógica adicional de negócios pode ser escrita aqui
}
4.2. Criando Entidades
Criar uma entidade de Usuário envolve a construção de um novo objeto de entidade e persistindo-o no banco de dados usando o método Save
ou SaveX
. O código a seguir demonstra como criar uma nova entidade de Usuário e inicializar dois campos age
e name
.
// A função CreateUser é usada para criar uma nova entidade de Usuário
func CreateUser(ctx context.Context, cliente *ent.Client) (*ent.User, error) {
// Use client.User.Create() para construir a solicitação de criação de um Usuário,
// em seguida, encadear os métodos SetAge e SetName para definir os valores dos campos da entidade.
u, err := cliente.User.
Create().
SetAge(30). // Definir idade do usuário
SetName("a8m"). // Definir nome do usuário
Save(ctx) // Chame Save para salvar a entidade no banco de dados
if err != nil {
return nil, fmt.Errorf("falha ao criar usuário: %w", err)
}
log.Println("usuário foi criado: ", u)
return u, nil
}
Na função main
, você pode chamar a função CreateUser
para criar uma nova entidade de usuário.
func main() {
// ...Código omitido de estabelecimento de conexão com o banco de dados
// Criar uma entidade de usuário
u, err := CreateUser(ctx, cliente)
if err != nil {
log.Fatalf("falha ao criar usuário: %v", err)
}
log.Printf("usuário criado: %#v\n", u)
}
4.3. Consultando Entidades
Para consultar entidades, podemos usar o construtor de consulta gerado pelo ent
. O código a seguir demonstra como consultar um usuário chamado "a8m":
// A função QueryUser é usada para consultar a entidade de usuário com um nome especificado
func QueryUser(ctx context.Context, cliente *ent.Client) (*ent.User, error) {
// Use client.User.Query() para construir a consulta de Usuário,
// em seguida, encadear o método Where para adicionar condições de consulta, como consultar por nome de usuário
u, err := cliente.User.
Query().
Where(user.NameEQ("a8m")). // Adicionar condição de consulta, neste caso, o nome é "a8m"
Only(ctx) // O método Only indica que apenas um resultado é esperado
if err != nil {
return nil, fmt.Errorf("falha ao consultar usuário: %w", err)
}
log.Println("usuário retornado: ", u)
return u, nil
}
Na função main
, você pode chamar a função QueryUser
para consultar a entidade de usuário.
func main() {
// ...Código omitido de estabelecimento de conexão com o banco de dados e criação de usuário
// Consultar a entidade de usuário
u, err := QueryUser(ctx, cliente)
if err != nil {
log.Fatalf("falha ao consultar usuário: %v", err)
}
log.Printf("usuário consultado: %#v\n", u)
}
5.1. Compreensão de Pontas e Pontas Inversas
No framework ent
, o modelo de dados é visualizado como uma estrutura de grafo, onde as entidades representam nós no grafo, e os relacionamentos entre entidades são representados por pontas. Uma ponta é uma conexão de uma entidade para outra, por exemplo, um Usuário
pode possuir vários Carros
.
As Pontas Inversas são referências reversas às pontas, representando logicamente o relacionamento inverso entre entidades, mas sem criar um novo relacionamento no banco de dados. Por exemplo, através da ponta inversa de um Carro
, podemos encontrar o Usuário
que possui este carro.
A principal importância das pontas e pontas inversas está em tornar a navegação entre entidades associadas muito intuitiva e direta.
Dica: No
ent
, as pontas correspondem às chaves estrangeiras do banco de dados tradicional e são usadas para definir relacionamentos entre tabelas.
5.2. Definindo Pontas no Esquema
Primeiramente, usaremos o CLI ent
para criar o esquema inicial para Carro
e Grupo
:
go run -mod=mod entgo.io/ent/cmd/ent new Car Group
Em seguida, no esquema do Usuário
, definimos a ponta com Carro
para representar o relacionamento entre usuários e carros. Podemos adicionar uma ponta carros
apontando para o tipo Carro
na entidade do usuário, indicando que um usuário pode ter vários carros:
// entdemo/ent/schema/user.go
// Pontas do Usuário.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("carros", Car.Type),
}
}
Após definir as pontas, precisamos executar go generate ./ent
novamente para gerar o código correspondente.
5.3. Operando em Dados de Ponta
Criar carros associados a um usuário é um processo simples. Dada uma entidade de usuário, podemos criar uma nova entidade de carro e associá-la ao usuário:
import (
"context"
"log"
"entdemo/ent"
// Certifique-se de importar a definição do esquema para Carro
_ "entdemo/ent/schema"
)
func CreateCarsForUser(ctx context.Context, client *ent.Client, userID int) error {
user, err := client.User.Get(ctx, userID)
if err != nil {
log.Fatalf("falha ao obter o usuário: %v", err)
return err
}
// Criar um novo carro e associá-lo ao usuário
_, err = client.Car.
Create().
SetModel("Tesla").
SetRegisteredAt(time.Now()).
SetOwner(user).
Save(ctx)
if err != nil {
log.Fatalf("falha ao criar carro para o usuário: %v", err)
return err
}
log.Println("carro foi criado e associado ao usuário")
return nil
}
Da mesma forma, consultar os carros de um usuário é direto. Se quisermos recuperar uma lista de todos os carros pertencentes a um usuário, podemos fazer o seguinte:
func QueryUserCars(ctx context.Context, client *ent.Client, userID int) error {
user, err := client.User.Get(ctx, userID)
if err != nil {
log.Fatalf("falha ao obter o usuário: %v", err)
return err
}
// Consultar todos os carros pertencentes ao usuário
carros, err := user.QueryCarros().All(ctx)
if err != nil {
log.Fatalf("falha ao consultar carros: %v", err)
return err
}
for _, carro := range carros {
log.Printf("carro: %v, modelo: %v", carro.ID, carro.Model)
}
return nil
}
Através dos passos acima, não apenas aprendemos como definir pontas no esquema, mas também demonstramos como criar e consultar dados relacionados a pontas.
6. Travessia e Consulta de Grafo
6.1. Compreensão das Estruturas de Grafo
No ent
, as estruturas de grafo são representadas por entidades e as pontas entre elas. Cada entidade é equivalente a um nó no grafo, e os relacionamentos entre entidades são representados por pontas, que podem ser de um para um, de um para muitos, muitos para muitos, etc. Essa estrutura de grafo torna as consultas e operações complexas em um banco de dados relacional simples e intuitivas.
6.2. Percorrendo Estruturas de Gráfico
Escrever código de Traversal de Gráfico envolve principalmente consultar e associar dados através das arestas entre entidades. Abaixo está um exemplo simples demonstrando como percorrer a estrutura de gráfico em ent
:
import (
"context"
"log"
"entdemo/ent"
)
// GraphTraversal é um exemplo de percorrer a estrutura de gráfico
func GraphTraversal(ctx context.Context, client *ent.Client) error {
// Consulta o usuário chamado "Ariel"
a8m, err := client.User.Query().Where(user.NameEQ("Ariel")).Only(ctx)
if err != nil {
log.Fatalf("Falha na consulta do usuário: %v", err)
return err
}
// Percorre todos os carros pertencentes a Ariel
cars, err := a8m.QueryCars().All(ctx)
if err != nil {
log.Fatalf("Falha na consulta dos carros: %v", err)
return err
}
for _, car := range cars {
log.Printf("Ariel tem um carro com o modelo: %s", car.Model)
}
// Percorre todos os grupos dos quais Ariel é membro
groups, err := a8m.QueryGroups().All(ctx)
if err != nil {
log.Fatalf("Falha na consulta dos grupos: %v", err)
return err
}
for _, g := range groups {
log.Printf("Ariel é membro do grupo: %s", g.Name)
}
return nil
}
O código acima é um exemplo básico de percorrer um gráfico, que primeiro consulta um usuário e depois percorre os carros e grupos do usuário.
7. Visualizando o Esquema do Banco de Dados
7.1. Instalando a Ferramenta Atlas
Para visualizar o esquema do banco de dados gerado pelo ent
, podemos usar a ferramenta Atlas. Os passos de instalação do Atlas são muito simples. Por exemplo, no macOS, você pode instalá-lo usando brew
:
brew install ariga/tap/atlas
Nota: Atlas é uma ferramenta de migração de banco de dados universal que pode lidar com a gestão da versão das estruturas de tabela para vários bancos de dados. Uma introdução detalhada ao Atlas será fornecida em capítulos posteriores.
7.2. Gerando ERD e Esquema SQL
Usar o Atlas para visualizar e exportar esquemas é muito direto. Após instalar o Atlas, você pode usar o seguinte comando para visualizar o Diagrama de Entidade-Relacionamento (ERD):
atlas schema inspect -d [database_dsn] --format dot
Ou gerar diretamente o Esquema SQL:
atlas schema inspect -d [database_dsn] --format sql
Onde [database_dsn]
aponta para o nome da fonte de dados (DSN) do seu banco de dados. Por exemplo, para um banco de dados SQLite, poderia ser:
atlas schema inspect -d "sqlite://file:ent.db?mode=memory&cache=shared" --format dot
A saída gerada por esses comandos pode ser posteriormente transformada em visualizações ou documentos usando as ferramentas respectivas.
8. Migração de Esquema
8.1. Migração Automática e Migração Versionada
ent suporta duas estratégias de migração de esquema: migração automática e migração versionada. A migração automática é o processo de inspecionar e aplicar alterações no esquema em tempo de execução, adequado para desenvolvimento e testes. A migração versionada envolve a geração de scripts de migração e requer uma revisão cuidadosa e testes antes da implantação em produção.
Dica: Para migração automática, consulte o conteúdo na seção 4.1.
8.2. Realizando Migração Versionada
O processo de migração versionada envolve a geração de arquivos de migração através do Atlas. Abaixo estão os comandos relevantes:
Para gerar arquivos de migração:
atlas migrate diff -d ent/schema/path --dir migrations/dir
Posteriormente, esses arquivos de migração podem ser aplicados ao banco de dados:
atlas migrate apply -d migrations/dir --url database_dsn
Seguindo este processo, você pode manter um histórico de migrações de banco de dados no sistema de controle de versão e garantir uma revisão cuidadosa antes de cada migração.
Dica: Consulte o código de exemplo em https://github.com/ent/ent/tree/master/examples/start