Содержание урока · 10 разделов
Возьмём типовую задачу: бэкенд с таблицей задач — создать, показать список, отредактировать, удалить. На чистом database/sql каждая операция расписывается руками: написать SQL, открыть строки, перечислить колонки в Scan ровно в том порядке, в каком они идут в SELECT, закрыть rows и не забыть проверить ошибку после цикла. Работает — но каждый новый столбец требует правки сразу в трёх местах: в SQL, в Scan и в структуре. Именно на этом стыке и появляются самые нудные баги: колонку добавили в запрос, а в Scan забыли, и приложение падает уже в рантайме. ORM — Object-Relational Mapping, «объектно-реляционное отображение» — выносит эту рутину в библиотеку: вы объявляете структуру, а маппинг «колонка-поле», сборка запросов и сканирование делает она. В Go самой популярной такой библиотекой больше десяти лет остаётся GORM, и этот курс — про неё.
Как это выглядит на чистом database/sql
GOfunc listTodos(db *sql.DB, userID int64) ([]Todo, error) {
rows, err := db.Query(
"SELECT id, title, done FROM todos WHERE user_id = $1", userID)
if err != nil {
return nil, err
}
defer rows.Close()
var todos []Todo
for rows.Next() {
var t Todo
if err := rows.Scan(&t.ID, &t.Title, &t.Done); err != nil {
return nil, err
}
todos = append(todos, t)
}
return todos, rows.Err()
}Пятнадцать строк на одну выборку — и ни одна из них не содержит бизнес-логики. Scan привязан к порядку колонок: поменяете их местами в SELECT — данные молча съедут в чужие поля. rows.Err() после цикла забывается чаще всего, и обрыв соединения маскируется под пустой список. Именно эту лестницу GORM сворачивает в одну строку.
То же самое на GORM
GOfunc listTodos(gdb *gorm.DB, userID int64) ([]Todo, error) {
var todos []Todo
err := gdb.Where("user_id = ?", userID).Find(&todos).Error
return todos, err
}Find сам напишет SELECT, пройдёт по строкам и разложит колонки по полям структуры — по именам, а не по порядку. Where принимает плейсхолдер ?, в который параметр уходит через драйвер, без конкатенации строк: SQL-инъекцию так собрать не выйдет. Ошибка возвращается полем Error у результата цепочки.
Что такое ORM: мост между строками и структурами#
Реляционная база живёт таблицами со строками и колонками, Go — структурами и срезами. Между этими двумя мирами всегда есть трение, его называют impedance mismatch: типы не совпадают, NULL некуда класть, связи надо собирать вручную из внешних ключей. ORM — это слой, который берёт трение на себя: знает, что структура User соответствует таблице users, что поле Email — это колонка email, и что при сохранении объекта нужно собрать INSERT, а при чтении — просканировать строки обратно в структуры.
Важно понимать, что GORM не отменяет SQL — он его генерирует. Каждая цепочка методов вроде Where(...).Order(...).Limit(...) превращается в конкретный запрос, который можно увидеть в логах и проверить глазами. Поэтому ORM — это не «программирование без SQL», а другой способ его писать: вместо текста запроса вы описываете намерение, а текст собирает библиотека. Если SQL вы знаете хорошо, GORM становится предсказуемым: вы всегда догадываетесь, какой запрос получится. Если не знаете — ORM превращается в генератор неэффективных запросов, и это главный скрытый риск всей категории инструментов.
История GORM: v1, v2 и путь модуля gorm.io/gorm#
GORM написал Jinzhu Zhang, первая версия появилась в 2013 году и распространялась как github.com/jinzhu/gorm. Библиотека быстро стала дефолтным ответом на вопрос «как с базой в Go» — не в последнюю очередь потому, что в стандартной библиотеке долгое время не было ничего, кроме низкоуровневого database/sql. В 2020 году автор выпустил полностью переписанную вторую версию: новый модуль gorm.io/gorm, собственный парсер схем, диалекты как подключаемые пакеты, цепочечный API с клонированием запросов. Весь этот курс — про вторую версию; если в старых статьях видите импорт jinzhu/gorm, перед вами первый GORM, и его синтаксис местами отличается.
Сегодня GORM — самая звёздная ORM в экосистеме Go (больше тридцати тысяч звёзд на GitHub на момент написания), с документацией на gorm.io и большим количеством готовых плагинов: драйверы для разных СУБД, пагинация, трассировка, шифрование. Обратная сторона популярности — почти на любой вопрос по GORM ответ уже лежит в Stack Overflow, а сообщение об ошибке легко гуглится.
Что GORM берёт на себя#
- CRUD через цепочечный API: Create, First/Find, Where, Update, Delete — и сборка SQL под конкретную СУБД
- Миграции схемы: AutoMigrate создаёт и обновляет таблицы по структурам (урок 04)
- Ассоциации: belongs to, has one, has many, many-to-many и жадная загрузка Preload (уроки 11–14)
- Транзакции: обёртка db.Transaction с откатом по ошибке (урок 15)
- Хуки жизненного цикла: BeforeCreate, AfterUpdate и другие (урок 16)
- Мягкое удаление: колонка deleted_at вместо физического DELETE (урок 09)
- Лазейка для сырого SQL: Raw и Exec, когда генератора не хватает (урок 10)
- Пул соединений и контекст: обёртка над стандартным database/sql, поддержка WithContext
Четыре способа работать с базой из Go#
| Подход | Что пишете вы | Сильная сторона | Слабая сторона |
|---|---|---|---|
| database/sql | SQL и Scan вручную | полный контроль, ноль магии | многословно, всё руками |
| GORM | структуры + цепочки методов | скорость разработки, ассоциации, миграции | неэффективный SQL при незнании базы |
| sqlc | чистый SQL-файл | типобезопасный код из ваших запросов | сложные динамические фильтры |
| ent | схема как код | строгая типизация графа связей | высокий порог входа |
Выбор между подходами — это выбор между стоимостью старта и стоимостью сопровождения. database/sql бесплатен в зависимостях, но платит многословием на каждом запросе. sqlc генерирует из ваших SQL-запросов типизированный Go-код — за деталями идите в курс sqlc. ent описывает схему данных как код с сильной типизацией графа связей — у нас есть отдельный курс ent. GORM занимает середину: минимум церемоний на старте и достаточно места, чтобы выстрелить себе в ногу сложным запросом. Для CRUD-приложений — админок, каталогов, бэкендов с формами — эта середина почти всегда выгодна. Для аналитических систем с тяжёлыми отчётами расклад другой, и об этом честно поговорим ниже.
Первый взгляд на код с GORM: Create и First#
Чтобы почувствовать библиотеку, посмотрим на минимальный рабочий фрагмент: структура, сохранение записи и чтение по первичному ключу. Пока не вникайте в gorm.Open и типы — им посвящён следующий урок; сейчас важно увидеть общий ритм работы.
Код
GOtype User struct {
gorm.Model
Name string
Email string `gorm:"uniqueIndex"`
}
// сохранить
user := User{Name: "Анна", Email: "anna@example.com"}
result := db.Create(&user)
// INSERT INTO "users" ("name","email","created_at",...)
// VALUES ('Анна','anna@example.com',...) RETURNING "id"
// прочитать по первичному ключу
var found User
err := db.First(&found, user.ID).Error
// SELECT * FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" LIMIT 1Встроенный gorm.Model добавляет четыре служебных поля: ID, CreatedAt, UpdatedAt и DeletedAt — о них подробно в уроке 03. Create сохраняет структуру одной строкой и возвращает сгенерированный ID прямо в объект: в PostgreSQL это возможно благодаря RETURNING. First читает запись по первичному ключу, сортируя по id и ограничивая выборку одной строкой; если записи нет, вернётся gorm.ErrRecordNotFound, а не паника.
Когда GORM вредит: моё мнение#
GORM отлично закрывает CRUD, но есть класс задач, где я его сознательно не применяю: аналитические отчёты, оконные функции, сравнения периодов, агрегаты по десяткам тысяч строк. Запросы такого сорта пишутся на SQL быстро и понятно, а на цепочках методов — плохо: получается читаемый только автору конструктор, который ещё и генерирует неоптимальный план. Моё правило: если больше половины запросов модуля — агрегаты и отчёты, этот модуль лучше держать на сыром SQL (GORM умеет Raw/Exec, урок 10, либо sqlc рядом). Если же это типовой сервис с формами и списками — GORM окупается с первого дня.
Второй спорный пункт — теги вместо явной схемы. Схема данных живёт в комментариях к полям структуры, и «единственный источник правды» размазывается между кодом и базой. В команде с DBA это источник споров; в solo-проекте или стартапе — честная экономия времени. Выбирайте по составу команды, а не по хайпу.
Браузерный интерпретатор этого сайта исполняет только чистый Go без сети и файловой системы, поэтому код с GORM в уроках не запускается в песочнице. Чтобы гонять примеры руками, нужен локальный Go и база: проще всего начать с SQLite (файл вместо сервера) или поднять PostgreSQL в Docker. Учтите: gorm.io/driver/sqlite требует CGO (нужен компилятор C), а если хочется чистого Go — есть драйвер github.com/glebarez/sqlite. Драйвер PostgreSQL — gorm.io/driver/postgres — работает без CGO.
Понадобятся структуры, указатели, методы и ошибки — всё это есть в базовом курсе. Интерактивы урока (тесты, «заполни пропуск», «собери программу») дают XP и работают прямо в браузере, так что базу проверите даже без локальной базы данных.
Проверка: основы GORM
Какой путь модуля у GORM второй версии?
Проверка: как работает маппинг
Find сканирует строки результата в структуры. Как колонки попадают в поля?
Заполните пропуски: выборка на GORM
Восстановите цепочку запроса: фильтр по email и чтение одной записи по первичному ключу.
Соберите минимальную программу с GORM
Расставьте строки в правильном порядке: от объявления пакета до сохранения записи.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: словарь GORM
Что дальше по курсу#
Во втором уроке подключим базу данных: gorm.Open, DSN для PostgreSQL и SQLite, пул соединений. В третьем — модели и соглашения: как из структуры получается таблица, зачем нужен gorm.Model и что умеют теги. Дальше — миграции, CRUD, ассоциации, транзакции и итоговый проект: слой данных блога. Если захочется сверяться с официальной документацией, всё то же самое изложено на gorm.io — наш курс идёт параллельно ей, но по-русски и с разбором граблей.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- ORM — слой между структурами Go и таблицами: маппинг полей, сборка SQL, сканирование строк.
- GORM не отменяет SQL — он его генерирует; понимать итоговый запрос обязан сам разработчик.
- Актуальная версия — v2 с модулем gorm.io/gorm; jinzhu/gorm — устаревшая v1.
- GORM закрывает CRUD, ассоциации, транзакции, хуки, миграции и мягкое удаление.
- Для отчётов и оконных функций я беру сырой SQL или sqlc — цепочки методов там мешают.
- Песочница не исполняет GORM: нужен локальный Go и SQLite/Postgres для практики.
Домашнее задание
Соберите локальный стартовый модуль: go mod init demo, установите gorm.io/gorm и gorm.io/driver/sqlite (или postgres), объявите структуру User с gorm.Model, создайте через AutoMigrate таблицу и сохраните запись через Create. Затем прочитайте её по ID через First и выведите имя в консоль. Если знакомы с sqlite3 CLI — загляните в файл базы и найдите свои колонки.
Личные заметкиПусто
Вопросы и ответы по уроку
Что такое GORM простыми словами?
Библиотека, которая по вашим структурам Go сама пишет SQL: сохраняет объекты строками таблиц, читает их обратно и ведёт соединения с базой. Вы описываете данные, GORM — запросы.
Нужен ли GORM, если я хорошо знаю SQL?
Да, но с оговоркой: GORM экономит время на CRUD и миграциях, а сложные отчёты вы всё равно пишете сырым SQL через Raw или рядом с sqlc. Знание SQL делает работу с ORM предсказуемой.
Чем GORM отличается от sqlc и ent?
GORM — классический ORM с цепочками методов и автомиграциями. sqlc генерирует код из ваших SQL-файлов, ent — строит строго типизированную схему-граф. GORM быстрее всех в старт, sqlc строже в контроле запросов.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
sqlc: типобезопасный код из SQL-запросов
Курс sqlc: настройка, запросы, параметры, NULL-типы, транзакции, джойны, пагинация, CI и сравнение с ORM. 25 уроков с практикой.
- Настраивать sqlc.yaml для Postgres/MySQL/SQLite
- Писать аннотированные запросы с типизированными параметрами
- Работать с NULL, массивами и JSON-колонками
- Организовывать транзакции через WithTx