ent для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое ent и зачем он нужен?

ent — это мощная ORM (Object-Relational Mapping) библиотека для языка Go, которая использует подход генерации кода. В отличие от традиционных ORM, где вы пишете структуры и аннотации вручную, ent генерирует весь код на основе вашей схемы данных, описанной на Go.

Основные преимущества ent:

  • Безопасность типов: Все запросы проверяются на этапе компиляции, что исключает ошибки времени выполнения, связанные с неправильными именами полей или типов.
  • Генерация кода: Вы описываете схему один раз, а ent генерирует все необходимые структуры, конструкторы запросов, миграции и даже тесты.
  • Производительность: Сгенерированный код оптимизирован и не использует рефлексию, что делает его быстрее многих других ORM.
  • Графовые запросы: ent позволяет легко выполнять запросы с загрузкой связанных сущностей (Eager Loading) и строить сложные графовые запросы.
  • Поддержка множества баз данных: MySQL, PostgreSQL, SQLite, Gremlin (для графовых баз) и другие.

ent идеально подходит для проектов, где важна надежность, производительность и строгая типизация. Он часто используется в микросервисной архитектуре, высоконагруженных системах и проектах, где требуется четкая структура данных.

Установка

Установка ent включает два компонента: сам пакет и CLI-инструмент для генерации кода.

# Установка CLI-инструмента (один раз)
go install entgo.io/ent/cmd/ent@latest

# Инициализация проекта (создание папки ent и схемы)
ent init User

# Установка зависимостей
go mod tidy

После выполнения этих команд у вас появится папка ent/ с файлом schema/user.go, где вы будете описывать схему данных.

Быстрый старт с примером кода

Давайте создадим простую модель пользователя и научимся выполнять базовые операции.

1. Описание схемы (ent/schema/user.go)

package schema

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

// User holds the schema definition for the User entity.
type User struct {
    ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").
            NotEmpty().
            Unique(),
        field.Int("age").
            Positive(),
        field.String("email").
            Unique(),
    }
}

2. Генерация кода

go generate ./ent

Эта команда создаст все необходимые файлы: клиент, конструкторы запросов, миграции и т.д.

3. Использование в main.go

package main

import (
    "context"
    "fmt"
    "log"

    "your-project/ent"
    "your-project/ent/user"
    _ "github.com/mattn/go-sqlite3"
)

func main() {
    // Создаем клиент для SQLite (в памяти)
    client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
    if err != nil {
        log.Fatalf("failed opening connection to sqlite: %v", err)
    }
    defer client.Close()

    // Автомиграция
    ctx := context.Background()
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("failed creating schema resources: %v", err)
    }

    // Создание пользователя
    newUser, err := client.User.
        Create().
        SetName("Alice").
        SetAge(30).
        SetEmail("alice@example.com").
        Save(ctx)
    if err != nil {
        log.Fatalf("failed creating user: %v", err)
    }
    fmt.Println("Created user:", newUser.ID)

    // Запрос пользователя по имени
    foundUser, err := client.User.
        Query().
        Where(user.Name("Alice")).
        Only(ctx)
    if err != nil {
        log.Fatalf("failed querying user: %v", err)
    }
    fmt.Printf("Found user: %+v\n", foundUser)
}

Основные методы и функции

1. Create() — Создание записи

Что делает: Начинает построение запроса на создание новой записи в базе данных.

Параметры: Нет прямых параметров, но после вызова используются сеттеры (Set*).

user, err := client.User.
    Create().
    SetName("Bob").
    SetAge(25).
    SetEmail("bob@example.com").
    Save(ctx)

2. Query() — Запрос записей

Что делает: Начинает построение запроса на выборку записей.

Параметры: Нет прямых параметров.

users, err := client.User.
    Query().
    Where(user.AgeGT(18)).
    All(ctx)

3. Where() — Фильтрация

Что делает: Добавляет условия фильтрации к запросу. Может быть вызван несколько раз для добавления нескольких условий.

Параметры: Принимает одно или несколько условий (predicates), создаваемых через сгенерированные функции (например, user.NameEQ, user.AgeGT).

// Поиск пользователей старше 18 лет с именем Alice
users, err := client.User.
    Query().
    Where(
        user.AgeGT(18),
        user.NameEQ("Alice"),
    ).
    All(ctx)

4. All() — Получение всех записей

Что делает: Выполняет запрос и возвращает все найденные записи в виде среза.

Параметры: Принимает context.Context.

users, err := client.User.Query().All(ctx)

5. Only() — Получение единственной записи

Что делает: Выполняет запрос и возвращает единственную запись. Если записей больше одной или ноль — возвращает ошибку.

Параметры: Принимает context.Context.

user, err := client.User.
    Query().
    Where(user.ID(1)).
    Only(ctx)

6. First() — Получение первой записи

Что делает: Выполняет запрос и возвращает первую запись (с сортировкой по умолчанию). Если записей нет — возвращает ошибку.

Параметры: Принимает context.Context.

user, err := client.User.
    Query().
    Order(ent.Asc(user.FieldID)).
    First(ctx)

7. Update() — Обновление записей

Что делает: Начинает построение запроса на обновление записей.

Параметры: Нет прямых параметров.

updated, err := client.User.
    Update().
    Where(user.ID(1)).
    SetAge(31).
    Save(ctx)

8. Delete() — Удаление записей

Что делает: Начинает построение запроса на удаление записей.

Параметры: Нет прямых параметров.

deleted, err := client.User.
    Delete().
    Where(user.ID(1)).
    Exec(ctx)

9. Order() — Сортировка

Что делает: Добавляет сортировку к запросу.

Параметры: Принимает один или несколько порядков (ent.Asc, ent.Desc).

users, err := client.User.
    Query().
    Order(ent.Asc(user.FieldName)).
    All(ctx)

10. Limit() / Offset() — Пагинация

Что делает: Ограничивает количество возвращаемых записей и задает смещение.

Параметры: Limit принимает int, Offset принимает int.

users, err := client.User.
    Query().
    Limit(10).
    Offset(20).
    All(ctx)

11. Count() — Подсчет записей

Что делает: Возвращает количество записей, удовлетворяющих условиям.

Параметры: Принимает context.Context.

count, err := client.User.
    Query().
    Where(user.AgeGT(18)).
    Count(ctx)

12. With*() — Загрузка связей (Eager Loading)

Что делает: Загружает связанные сущности (например, если у пользователя есть pets).

Параметры: Может принимать функции для дополнительной фильтрации связанных записей.

users, err := client.User.
    Query().
    WithPets().
    All(ctx)
// Каждый user.Pets будет содержать срез связанных записей Pet

Реальный практический пример использования

Давайте создадим более сложный пример: систему управления задачами (Task Manager) с пользователями, проектами и задачами.

Схемы данных

// ent/schema/user.go
package schema

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

type User struct {
    ent.Schema
}

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").NotEmpty().Unique(),
        field.String("email").NotEmpty().Unique(),
    }
}

func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("tasks", Task.Type),
        edge.To("projects", Project.Type),
    }
}

// ent/schema/project.go
package schema

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

type Project struct {
    ent.Schema
}

func (Project) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").NotEmpty(),
        field.String("description").Default(""),
    }
}

func (Project) Edges() []ent.Edge {
    return []ent.Edge{
        edge.From("owner", User.Type).Ref("projects").Unique(),
        edge.To("tasks", Task.Type),
    }
}

// ent/schema/task.go
package schema

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

type Task struct {
    ent.Schema
}

func (Task) Fields() []ent.Field {
    return []ent.Field{
        field.String("title").NotEmpty(),
        field.String("description").Default(""),
        field.Enum("status").Values("todo", "in_progress", "done").Default("todo"),
        field.Time("created_at"),
    }
}

func (Task) Edges() []ent.Edge {
    return []ent.Edge{
        edge.From("assignee", User.Type).Ref("tasks").Unique(),
        edge.From("project", Project.Type).Ref("tasks").Unique(),
    }
}

Использование в main.go

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "your-project/ent"
    "your-project/ent/task"
    _ "github.com/mattn/go-sqlite3"
)

func main() {
    client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
    if err != nil {
        log.Fatalf("failed opening connection: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("failed creating schema: %v", err)
    }

    // Создаем пользователя
    user, err := client.User.Create().
        SetName("Alice").
        SetEmail("alice@example.com").
        Save(ctx)
    if err != nil {
        log.Fatalf("failed creating user: %v", err)
    }

    // Создаем проект
    project, err := client.Project.Create().
        SetName("My Project").
        SetDescription("A sample project").
        SetOwner(user).
        Save(ctx)
    if err != nil {
        log.Fatalf("failed creating project: %v", err)
    }

    // Создаем задачи
    task1, err := client.Task.Create().
        SetTitle("Setup database").
        SetDescription("Initialize the database schema").
        SetStatus("todo").
        SetCreatedAt(time.Now()).
        SetAssignee(user).
        SetProject(project).
        Save(ctx)
    if err != nil {                    

Рекомендаций