Какие инструменты для управления миграциями баз данных вы использовали в Go-проектах и почему?

Ответ

Да, я регулярно работал с миграциями баз данных. В Go-проектах наиболее популярны два подхода: использование .sql файлов и написание миграций на самом Go.

Я использовал следующие инструменты:

  1. golang-migrate/migrate: Это, пожалуй, самая популярная и универсальная библиотека. Она поддерживает огромное количество баз данных и источников миграций (файлы, go-bindata, S3 и т.д.).

    • Подход: Используются .sql файлы с определенным форматом именования, например: 000001_create_users_table.up.sql и 000001_create_users_table.down.sql.
    • Пример ...up.sql файла:
      CREATE TABLE users (
          id UUID PRIMARY KEY,
          name VARCHAR(100) NOT NULL,
          email VARCHAR(100) UNIQUE NOT NULL,
          created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
      );
  2. pressly/goose: Еще один популярный инструмент, который также делает упор на .sql файлы, но с другим синтаксисом внутри файла.

    • Подход: Используются комментарии -- +goose Up и -- +goose Down в одном SQL-файле.
    • Пример файла миграции:

      -- +goose Up
      CREATE TABLE products (
          id SERIAL PRIMARY KEY,
          name TEXT NOT NULL,
          price NUMERIC(10, 2) NOT NULL
      );
      
      -- +goose Down
      DROP TABLE products;
  3. Программные миграции: Иногда, если логика миграции сложна (например, требует переноса и трансформации данных), миграции пишутся на Go. Это можно сделать с помощью того же golang-migrate/migrate или других библиотек.

    • Пример запуска миграций из кода:

      import "github.com/golang-migrate/migrate/v4"
      // ... импорты драйверов БД и источников
      
      func RunMigrations(databaseURL, migrationsPath string) error {
          m, err := migrate.New(
              "file://" + migrationsPath, // источник миграций
              databaseURL,               // URL подключения к БД
          )
          if err != nil {
              return err
          }
          // Применяем все доступные миграции "вверх"
          if err := m.Up(); err != nil && err != migrate.ErrNoChange {
              return err
          }
          return nil
      }

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

Ответ 18+ 🔞

А, миграции, блядь! Ну, это ж святое, ёпта! Без них нихуя не работает, как без костылей инвалид. Я с этой хуйней регулярно сталкиваюсь, да.

В Гошных проектах, сука, обычно два лагеря: одни за чистые SQL-файлы, другие — за код на Go. Это как спор, хуй с горы или манда с ушами — у каждого свои аргументы.

Вот что я юзал, на самом деле:

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

    • Как работает: Ты пишешь два файлика, сука. Один — чтобы накатить (up.sql), другой — чтобы откатить (down.sql). Имена у них, блядь, строгие, нумерованные, чтобы порядок не сбить, а то пиздец наступит.
    • Вот смотри, как выглядит файл наката (...up.sql):
      CREATE TABLE users (
          id UUID PRIMARY KEY,
          name VARCHAR(100) NOT NULL,
          email VARCHAR(100) UNIQUE NOT NULL,
          created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
      );

      А в down.sql будет DROP TABLE users;. Всё, пизда, коротко и ясно.

  2. pressly/goose — тоже, сука, монстр известный. Тут подход чуток иной, но суть та же.

    • Фишка в чём: Всё в один файл запихивается, но разделяется волшебными комментариями. Типа, вот тут, блядь, мы поднимаемся (-- +goose Up), а вот тут — откатываемся в пизду (-- +goose Down).
    • Пример одной миграции в одном файле:

      -- +goose Up
      CREATE TABLE products (
          id SERIAL PRIMARY KEY,
          name TEXT NOT NULL,
          price NUMERIC(10, 2) NOT NULL
      );
      
      -- +goose Down
      DROP TABLE products;

      Удобно, ёпта, всё в одном месте. Не надо, как дурак, между двумя файлами прыгать.

  3. А вот это, блядь, для извращенцев — миграции прямо на Go. Это когда твоя логика настолько ебнутая, что простым SQL не отделаешься. Ну там, данные перенести, преобразовать, нахуй, всё такое.

    • Тут уже пишешь код, сука. Можно через ту же библиотеку golang-migrate, но в качестве источника указать, что миграции — это Go-функции. Выглядит, конечно, мощно, но и ответственности, блядь, дохуя.
    • Вот, на коленке, как это примерно из кода запускается:

      import "github.com/golang-migrate/migrate/v4"
      // ... остальные импорты, без них нихуя
      
      func RunMigrations(databaseURL, migrationsPath string) error {
          m, err := migrate.New(
              "file://" + migrationsPath, // вот отсюда, сука, файлы тащи
              databaseURL,               // а сюда коннекться
          )
          if err != nil {
              return err // если ошибка — всё, пиздец, приехали
          }
          // И тут команда: "Всем подняться, блядь!"
          if err := m.Up(); err != nil && err != migrate.ErrNoChange {
              return err // если уже всё накатано — ок, не дергаемся
          }
          return nil
      }

А какой выбрать, спрашиваешь? Да похуй, в общем-то. Для 95% проектов, где просто таблицы создавать или поля добавлять — бери golang-migrate с SQL-файлами. Просто, наглядно, и от твоего кода не зависит, блядь. А вот эти программные миграции на Go — это уже для особых случаев, когда мозг выебать надо. Как говорится, волнение ебать, а терпения — ноль.