Что такое 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 { Также в библиотеке
Работа с JWT токенами в TypeScript: библиотека jsonwebtoken и полное руководство
GraphQL библиотека JavaScript (Node.js) — Полное руководство
gRPC для Swift (iOS/macOS): библиотеки, настройка и примеры
Конфигурация приложений Go (Golang): библиотеки и инструменты