1. Обзор механизма миграции

1.1 Концепция и роль миграции

Миграция базы данных - это процесс синхронизации изменений в моделях данных с структурой базы данных, что является важным для сохранения данных. По мере итерации версий приложения модель данных часто меняется, например, добавляются или удаляются поля или изменяются индексы. Миграция позволяет разработчикам управлять этими изменениями в версионированной и систематической manере, обеспечивая согласованность между структурой базы данных и моделью данных.

В современной веб-разработке механизм миграции обеспечивает следующие преимущества:

  1. Контроль версий: Файлы миграции позволяют отслеживать историю изменений структуры базы данных, что удобно для отката и понимания изменений в каждой версии.
  2. Автоматизированное развертывание: Через механизм миграции можно автоматизировать развертывание и обновление базы данных, что уменьшает возможность ручного вмешательства и риск ошибок.
  3. Коллаборация в команде: Файлы миграции гарантируют, что участники команды используют синхронизированные структуры базы данных в различных средах разработки, облегчая совместную разработку.

1.2 Особенности миграции фреймворка ent

Интеграция фреймворка ent с механизмом миграции предлагает следующие возможности:

  1. Декларативное программирование: Разработчикам нужно лишь сосредоточиться на Go-представлении сущностей, и фреймворк ent обработает преобразование сущностей в таблицы базы данных.
  2. Автоматическая миграция: ent может автоматически создавать и обновлять структуры таблиц базы данных без необходимости вручную писать операторы DDL.
  3. Гибкое управление: ent предоставляет различные варианты конфигурации для поддержки различных требований к миграции, таких как c или без ограничений внешнего ключа и генерация глобально уникальных идентификаторов.

2. Введение в автоматическую миграцию

2.1 Основные принципы автоматической миграции

Функция автоматической миграции фреймворка ent основана на файлах определения схемы (обычно находятся в каталоге ent/schema) для создания структуры базы данных. После определения сущностей и отношений разработчики, ent будет анализировать существующую структуру в базе данных и генерировать соответствующие операции для создания таблиц, добавления или изменения столбцов, создания индексов и т. д.

Более того, принцип автоматической миграции ent работает в "режиме добавления": по умолчанию он только добавляет новые таблицы, новые индексы или добавляет столбцы в таблицы, и не удаляет существующие таблицы или столбцы. Это полезно для предотвращения случайной потери данных и упрощает расширение структуры базы данных в прямом направлении.

2.2 Использование автоматической миграции

Основные шаги использования автоматической миграции ent следующие:

package main

import (
    "context"
    "log"
    "ent"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("Failed to connect to MySQL: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // Выполнение автоматической миграции для создания или обновления схемы базы данных
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("Failed to create database schema: %v", err)
    }
}

В приведенном выше коде ent.Open отвечает за установление соединения с базой данных и возвращает экземпляр клиента, а client.Schema.Create выполняет фактическую операцию автоматической миграции.

3. Расширенные применения автоматической миграции

3.1 Удаление столбцов и индексов

В некоторых случаях может потребоваться удалить столбцы или индексы, которые больше не нужны в схеме базы данных. В этом случае можно использовать параметры WithDropColumn и WithDropIndex. Например:

// Выполнение миграции с параметрами для удаления столбцов и индексов.
err = client.Schema.Create(
    ctx,
    migrate.WithDropIndex(true),
    migrate.WithDropColumn(true),
)

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

3.2 Универсальный идентификатор

По умолчанию первичные ключи в SQL-базах данных начинаются с 1 для каждой таблицы, и разные типы сущностей могут использовать одинаковые идентификаторы. В некоторых сценариях приложений, например, при использовании GraphQL, может потребоваться обеспечить всем объектам разных типов сущностей глобальную уникальность идентификаторов. В ent это можно настроить с помощью опции WithGlobalUniqueID:

// Выполнение миграции с универсально уникальными идентификаторами для каждой сущности.
if err := client.Schema.Create(ctx, migrate.WithGlobalUniqueID(true)); err != nil {
    log.Fatalf("Failed to create the database schema: %v", err)
}

После включения опции WithGlobalUniqueID ent будет присваивать каждой сущности в таблице с именем ent_types уникальный идентификатор из диапазона 2^32 для достижения глобальной уникальности.

3.3 Офлайн-режим

Офлайн-режим позволяет записывать изменения схемы в io.Writer вместо их непосредственного выполнения в базе данных. Это полезно для проверки SQL-команд перед тем, как изменения вступят в силу, или для создания SQL-скрипта для ручного выполнения. Например:

// Записать изменения миграции в файл
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("Не удалось создать файл миграции: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("Не удалось вывести изменения схемы базы данных: %v", err)
}

Этот код запишет изменения миграции в файл с именем migrate.sql. На практике разработчики могут выбирать прямой вывод в стандартный вывод или запись в файл для рецензирования или сохранения записей.

4. Поддержка внешних ключей и настраиваемые хуки

4.1 Включение или выключение внешних ключей

В Ent внешние ключи реализуются путем определения отношений (связей) между сущностями, и эти внешние ключевые отношения автоматически создаются на уровне базы данных для обеспечения целостности и согласованности данных. Однако в некоторых ситуациях, например, для оптимизации производительности или когда база данных не поддерживает внешние ключи, их можно отключить.

Для включения или отключения ограничений внешних ключей в миграциях можно управлять этим через параметр конфигурации WithForeignKeys:

// Включение внешних ключей
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true), 
)
if err != nil {
    log.Fatalf("Не удалось создать ресурсы схемы с внешними ключами: %v", err)
}

// Отключение внешних ключей
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false), 
)
if err != nil {
    log.Fatalf("Не удалось создать ресурсы схемы без внешних ключей: %v", err)
}

Этот параметр конфигурации должен быть передан при вызове Schema.Create и определяет, включать ли ограничения внешних ключей в сгенерированное DDL на основе указанного значения.

4.2 Применение настраиваемых хуков миграции

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

Вот пример реализации настраиваемых хуков миграции:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // Настраиваемый код, который будет выполнен перед миграцией
        // Например, журналирование, проверка определенных предпосылок и т. д.
        log.Println("Настраиваемая логика перед миграцией")
        
        // Вызов следующего хука или стандартной логики миграции
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }
        
        // Настраиваемый код, который будет выполнен после миграции
        // Например, очистка, миграция данных, проверки безопасности и т. д.
        log.Println("Настраиваемая логика после миграции")
        return nil
    })
}

// Использование настраиваемых хуков в миграции
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("Ошибка применения настраиваемых хуков миграции: %v", err)
}

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

5. Версионные миграции

5.1 Введение в версионные миграции

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

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

Автоматическая миграция часто является необратимой, генерируя и выполняя SQL-выражения для соответствия последнему состоянию сущностных моделей, в основном используется на этапах разработки или в небольших проектах.

5.2 Использование версионных миграций

1. Установка инструмента Atlas

Прежде чем использовать версионные миграции, вам необходимо установить инструмент Atlas на вашу систему. Atlas - это инструмент миграции, поддерживающий несколько систем управления базами данных и обладающий мощными возможностями для управления изменениями схемы базы данных.

macOS + Linux

curl -sSf https://atlasgo.sh | sh

Homebrew

brew install ariga/tap/atlas

Docker

docker pull arigaio/atlas
docker run --rm arigaio/atlas --help

Windows

https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe

2. Генерация файлов миграции на основе текущих определений сущностей

atlas migrate diff migration_name \
  --dir "file://ent/migrate/migrations" \
  --to "ent://ent/schema" \
  --dev-url "docker://mysql/8/ent"

3. Файлы миграции приложения

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

atlas migrate apply \
  --dir "file://ent/migrate/migrations" \
  --url "mysql://root:pass@localhost:3306/example"

Используйте команду atlas migrate apply, указав каталог файлов миграции (--dir) и URL целевой базы данных (--url) для применения файлов миграции.