Go: GORM

Теория: Миграции схемы базы

После подключения к базе и определения моделей возникает следующая задача: привести схему базы данных в соответствие с кодом. Модели описывают сущности и поля, но сама база пока ничего об этом не знает. Миграции схемы как раз и отвечают за то, чтобы таблицы создавались, обновлялись и не расходились с кодом. В GORM эту роль частично берёт на себя механизм AutoMigrate(): он читает структуры и автоматически применяет изменения к схеме.

AutoMigrate и создание таблиц

AutoMigrate() работает как мост между моделями и базой данных. Функция просматривает структуру, сравнивает её с текущей схемой и, при необходимости, создаёт недостающие таблицы и столбцы. Если таблицы ещё нет, GORM строит её с нуля. Если она уже существует, AutoMigrate() аккуратно добавляет новые поля, но не удаляет старые и не меняет типы существующих колонок. Такой режим безопасен для ранних этапов разработки, когда нужно быстро поднять рабочую базу.

Простейший пример миграции показывает эту механику на модели пользователя:

package main

import (
	"log"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// User описывает сущность «пользователь».
type User struct {
	ID    uint   // первичный ключ
	Name  string // имя
	Email string // электронная почта
	Age   int    // дата рождения
}

func main() {
	dsn := "host=localhost user=postgres password=postgres dbname=testdb port=5432 sslmode=disable"

	// Подключение к базе через GORM и драйвер postgres.
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatalf("ошибка подключения: %v", err)
	}

	// Автоматическая миграция схемы для модели User.
	if err := db.AutoMigrate(&User{}); err != nil {
		log.Fatalf("ошибка миграции: %v", err)
	}

	log.Println("Схема для users синхронизирована")
}

При запуске этого кода GORM проверяет наличие таблицы users. Если таблицы нет, она создаётся с колонками id, name, email и age. Если таблица существует, но, например, поле age было добавлено позже, AutoMigrate() создаст недостающий столбец. При этом существующие данные остаются на месте, а базовая структура обновляется под текущую модель.

Обновление схемы через код

Когда модель меняется, то же должно произойти и с базой. Добавление поля в структуру, изменение ограничений через теги или добавление служебных колонок приводят к необходимости обновить схему. В простых случаях это делается тем же вызовом AutoMigrate(). Код остаётся тем же, а миграция повторно сравнивает модель с таблицей и вносит изменения.

Например, если к модели User добавить временные поля и мягкое удаление, схема расширится:

import (
	"time"

	"gorm.io/gorm"
)

// User с временными полями и мягким удалением.
type User struct {
	ID        uint
	Name      string
	Email     string
	Age       int
	CreatedAt time.Time      // время создания
	UpdatedAt time.Time      // время обновления
	DeletedAt gorm.DeletedAt `gorm:"index"` // пометка удаления и индекс
}

Повторный вызов AutoMigrate():

if err := db.AutoMigrate(&User{}); err != nil {
	log.Fatalf("ошибка миграции: %v", err)
}

добавит в таблицу users новые колонки created_at, updated_at и deleted_at, если их не было. Индекс по deleted_at появится автоматически. Существующие строки получат NULL в новых полях, а всё последующее создание и обновление записей начнёт заполнять эти значения.

Через код удобно обновлять и связи между таблицами. Если к системе пользователей добавить заказы, модели и миграция могут выглядеть так:

// Order с внешним ключом на пользователя.
type Order struct {
	ID     uint
	UserID uint `gorm:"not null;index"` // ссылка на пользователя
	Amount int  // сумма заказа
}

// Одновременная миграция двух моделей.
if err := db.AutoMigrate(&User{}, &Order{}); err != nil {
	log.Fatalf("ошибка миграции: %v", err)
}

В этом сценарии GORM создаёт таблицу orders, добавляет колонку user_id и индекс по ней. Дополнительные ограничения, такие как каскадное обновление и поведение при удалении, можно описать через теги в модели Order(). AutoMigrate() учитывает эти настройки и отражает их в схеме, насколько это поддерживается выбранной СУБД.

Как миграции работают под капотом

Внутри AutoMigrate() действует по простой, но аккуратной схеме. Сначала GORM считывает описание структуры: имена полей, типы, теги, связи. Затем библиотека запрашивает у базы текущую информацию о таблицах и колонках. На этой основе формируется список различий: какие таблицы отсутствуют, какие поля нужно добавить, какие индексы стоит создать. После этого GORM генерирует и выполняет DDL-запросы: CREATE TABLE, ALTER TABLE ADD COLUMN, CREATE INDEX и другие.

Важно, что AutoMigrate() ориентирован на безопасные изменения. Он не удаляет столбцы, даже если поле исчезло из структуры. Он не меняет типы колонок, если они уже существуют. Такое поведение защищает от потери данных, но означает, что сложные изменения схемы (переименование поля, сжатие типов, удаление колонок) требуют явных миграций на уровне SQL или отдельных инструментов вроде goose. GORM в этом смысле ведёт себя как мягкий помощник: добавляет недостающее, но не ломает существующее.

Под капотом GORM также учитывает особенности разных СУБД. Типы данных, синтаксис создания индексов, поддержка ограничений и внешних ключей зависят от конкретного драйвера. AutoMigrate() переводит абстрактное описание модели в конкретные выражения PostgreSQL, MySQL или другой базы. Благодаря этому один и тот же код миграции может работать с разными системами, если модели заданы достаточно универсально.

В простых проектах AutoMigrate() способен полностью закрыть потребности в эволюции схемы. На этапе активной разработки он позволяет быстро изменять модели и получить актуальную структуру без написания DDL-скриптов. В более зрелых системах его обычно используют осторожно: для небольших дополнений и лайтовых изменений, а крупные рефакторинги схемы оформляют в виде явных миграций с контролем версий.

Подведем итоги

Миграции схемы в GORM строятся вокруг механизма AutoMigrate(). Структуры Go становятся источником правды о данных, а ORM переводит это описание в реальные таблицы и поля. Создание новых сущностей, добавление служебных колонок, настройка связей и индексов выполняются напрямую из кода. Такой подход снижает объём ручного SQL и делает схему ближе к модели предметной области. Важно помнить о границах AutoMigrate(): он умеет добавлять и расширять, но не отвечает за разрушительные изменения. Для сложных сценариев всегда остаётся возможность подключить отдельный инструмент миграций и сочетать оба подхода.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845