1. Przegląd mechanizmu migracji

1.1 Pojęcie i Rola Migracji

Migracja bazy danych to proces synchronizacji zmian w modelach danych ze strukturą bazy danych, co jest kluczowe dla trwałości danych. W miarę jak wersja aplikacji się inkrementuje, model danych często ulega zmianom, takim jak dodawanie lub usuwanie pól, lub modyfikacja indeksów. Migracja umożliwia programistom zarządzanie tymi zmianami w sposób zwersjonowany i systematyczny, zapewniając spójność między strukturą bazy danych a modelem danych.

W nowoczesnym rozwoju aplikacji internetowych mechanizm migracji zapewnia następujące korzyści:

  1. Kontrola wersji: Pliki migracyjne mogą śledzić historię zmian w strukturze bazy danych, umożliwiając wygodne cofnięcie się i zrozumienie zmian w każdej wersji.
  2. Automatyzowane wdrażanie: Dzięki mechanizmowi migracji, wdrażanie i aktualizacje bazy danych mogą być zautomatyzowane, zmniejszając możliwość interwencji manualnej i ryzyko błędów.
  3. Współpraca zespołowa: Pliki migracyjne zapewniają, że członkowie zespołu korzystają z zsynchronizowanych struktur baz danych w różnych środowiskach deweloperskich, ułatwiając rozwój współpracujący.

1.2 Funkcje Migracji w Frameworku ent

Integracja frameworku ent z mechanizmem migracji oferuje następujące funkcje:

  1. Programowanie deklaratywne: Programiści muszą się skupić tylko na reprezentacji encji w Go, a framework ent zajmie się konwersją encji na tabele bazy danych.
  2. Automatyczna migracja: ent może automatycznie tworzyć i aktualizować struktury tabel bazy danych bez konieczności ręcznego pisania instrukcji DDL.
  3. Elastyczna kontrola: ent zapewnia różne opcje konfiguracji, aby obsłużyć różne wymagania migracyjne, takie jak z lub bez ograniczeń klucza obcego, oraz generowanie globalnie unikalnych identyfikatorów.

2. Wprowadzenie do Automatycznej Migracji

2.1 Podstawowe Zasady Automatycznej Migracji

Funkcja automatycznej migracji frameworku ent opiera się na plikach definicji schematu (zazwyczaj znajdujących się w katalogu ent/schema), aby wygenerować strukturę bazy danych. Po zdefiniowaniu encji i relacji, ent będzie kontrolować istniejącą strukturę w bazie danych i generować odpowiadające operacje, takie jak tworzenie tabel, dodawanie lub modyfikowanie kolumn, tworzenie indeksów, itp.

Ponadto, zasada automatycznej migracji ent działa w trybie "dopisywania": domyślnie dodaje tylko nowe tabele, nowe indeksy lub dodaje kolumny do tabel, i nie usuwa istniejących tabel ani kolumn. Ten design jest korzystny dla zapobiegania przypadkowej utraty danych i ułatwia rozwijanie struktury bazy danych w sposób progresywny.

2.2 Korzystanie z Automatycznej Migracji

Podstawowe kroki do użycia automatycznej migracji w ent to:

package main

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

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("Nie udało się połączyć z MySQL: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // Wykonaj automatyczną migrację, aby utworzyć lub zaktualizować schemat bazy danych
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("Nie udało się utworzyć schematu bazy danych: %v", err)
    }
}

W powyższym kodzie, ent.Open jest odpowiedzialne za nawiązanie połączenia z bazą danych i zwrócenie egzemplarza klienta, a client.Schema.Create wykonuje rzeczywistą operację automatycznej migracji.

3. Zaawansowane Zastosowania Automatycznej Migracji

3.1 Usuwanie Kolumn i Indeksów

W niektórych przypadkach możemy potrzebować usunięcia kolumn lub indeksów, które już nie są potrzebne w schemacie bazy danych. W takim przypadku możemy użyć opcji WithDropColumn i WithDropIndex. Na przykład:

// Uruchom migrację z opcjami usuwania kolumn i indeksów.
err = client.Schema.Create(
    ctx,
    migrate.WithDropIndex(true),
    migrate.WithDropColumn(true),
)

Fragment kodu umożliwia konfigurację usuwania kolumn i indeksów podczas automatycznej migracji. ent usunie wszystkie kolumny i indeksy, które nie istnieją w definicji schematu podczas wykonywania migracji.

3.2 Globalny Unikalny ID

Domyślnie klucze główne w bazach danych SQL zaczynają się od 1 dla każdej tabeli, a różne rodzaje encji mogą udostępniać ten sam identyfikator. W niektórych przypadkach aplikacyjnych, takich jak korzystanie z GraphQL, może być konieczne zapewnienie globalnej unikalności dla identyfikatorów obiektów różnych rodzajów encji. W ent można to skonfigurować za pomocą opcji WithGlobalUniqueID:

// Uruchom migrację z uniwersalnie unikalnymi identyfikatorami dla każdej encji.
if err := client.Schema.Create(ctx, migrate.WithGlobalUniqueID(true)); err != nil {
    log.Fatalf("Nie udało się utworzyć schematu bazy danych: %v", err)
}

Po włączeniu opcji WithGlobalUniqueID, ent przypisze zakres identyfikatorów 2^32 do każdej encji w tabeli o nazwie ent_types, aby osiągnąć globalną unikalność.

3.3 Tryb offline

Tryb offline umożliwia zapisywanie zmian schematu do obiektu typu io.Writer zamiast wykonywania ich bezpośrednio w bazie danych. Jest to przydatne do weryfikowania poleceń SQL przed ich wprowadzeniem w życie lub do generowania skryptów SQL do ręcznego wykonania. Na przykład:

// Zapisz zmiany migracji do pliku
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("Nie udało się utworzyć pliku migracji: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("Nie udało się wydrukować zmian schematu bazy danych: %v", err)
}

Ten kod zapisze zmiany migracji do pliku o nazwie migrate.sql. W praktyce deweloperzy mogą wybrać bezpośrednie wyświetlanie w standardowym wyjściu lub zapis do pliku w celu przejrzenia lub zachowania.

4. Obsługa kluczy obcych i niestandardowe haki

4.1 Włączanie lub wyłączanie kluczy obcych

W Ent klucze obce są implementowane poprzez zdefiniowanie relacji (krawędzi) między encjami, a te związki kluczy obcych są automatycznie tworzone na poziomie bazy danych w celu zapewnienia integralności i spójności danych. Jednak w określonych sytuacjach, na przykład w celu optymalizacji wydajności lub gdy baza danych nie obsługuje kluczy obcych, można je wyłączyć.

Aby włączyć lub wyłączyć ograniczenia kluczy obcych w migracjach, można to kontrolować za pomocą opcji konfiguracji WithForeignKeys:

// Włącz klucze obce
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true), 
)
if err != nil {
    log.Fatalf("Nie udało się utworzyć zasobów schematu z kluczami obcymi: %v", err)
}

// Wyłącz klucze obce
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false), 
)
if err != nil {
    log.Fatalf("Nie udało się utworzyć zasobów schematu bez kluczy obcych: %v", err)
}

Ta opcja konfiguracji musi być przekazana podczas wywoływania Schema.Create i określa, czy mają być uwzględniane ograniczenia kluczy obcych w generowanym DDL w oparciu o określoną wartość.

4.2 Zastosowanie hoków migracyjnych

Haki migracyjne to niestandardowa logika, którą można wstawić i wykonać na różnych etapach migracji. Są one bardzo przydatne do wykonywania określonej logiki w bazie danych przed/po migracji, takiej jak weryfikacja wyników migracji i wypełnianie danych przed migracją.

Oto przykład implementacji niestandardowych hoków migracyjnych:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // Niestandardowy kod do wykonania przed migracją
        // Na przykład logowanie, sprawdzanie określonych warunków wstępnych, itp.
        log.Println("Niestandardowa logika przed migracją")
        
        // Wywołaj następny hak lub domyślną logikę migracji
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }
        
        // Niestandardowy kod do wykonania po migracji
        // Na przykład sprzątanie, migracja danych, sprawdzanie zabezpieczeń, itp.
        log.Println("Niestandardowa logika po migracji")
        return nil
    })
}

// Użycie niestandardowych hoków w migracji
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("Błąd stosowania niestandardowych hoków migracyjnych: %v", err)
}

Hoki są potężnymi i niezbędnymi narzędziami do skomplikowanych migracji, dającymi możliwość bezpośredniego kontrolowania zachowania migracji bazy danych w razie potrzeby.

5. Migracje z wersjonowaniem

5.1 Wprowadzenie do migracji z wersjonowaniem

Migracja z wersjonowaniem to wzorzec zarządzania migracją bazy danych, pozwalający deweloperom podzielić zmiany w strukturze bazy danych na wiele wersji, z których każda zawiera określony zestaw poleceń modyfikacji bazy danych. W porównaniu do Migracji Automatycznej, migracja z wersjonowaniem zapewnia bardziej szczegółową kontrolę, gwarantując śledzenie i odwracalność zmian w strukturze bazy danych.

Główną zaletą migracji z wersjonowaniem jest wsparcie dla migracji w przód i w tył (czyli aktualizacja lub przywrócenie poprzednich wersji), co pozwala deweloperom stosować, cofać lub pomijać określone zmiany w miarę potrzeby. Podczas współpracy w zespole migracja z wersjonowaniem zapewnia, że każdy członek pracuje na tej samej strukturze bazy danych, redukując problemy wynikające z niekonsekwencji.

Migracja Automatyczna jest często nieodwracalna, generując i wykonywując polecenia SQL, aby dopasować się do najnowszego stanu modeli encji, głównie używana we wczesnych etapach rozwoju lub w małych projektach.

5.2 Korzystanie z migracji z wersjonowaniem

1. Instalacja narzędzia Atlas

Zanim rozpoczniesz używać migracji wersji, musisz zainstalować narzędzie Atlas na swoim systemie. Atlas to narzędzie do migracji obsługujące wiele systemów baz danych, oferujące potężne funkcje do zarządzania zmianami schematu bazy danych.

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. Generowanie plików migracji na podstawie aktualnych definicji encji

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

3. Pliki migracji aplikacji

Po wygenerowaniu plików migracji mogą one być zastosowane w środowiskach rozwojowych, testowych lub produkcyjnych. Zazwyczaj najpierw zastosowałbyś te pliki migracji do bazy danych rozwojowej lub testowej, aby upewnić się, że wykonują się zgodnie z oczekiwaniami. Następnie te same kroki migracji zostaną wykonane w środowisku produkcyjnym.

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

Użyj polecenia atlas migrate apply, podając katalog plików migracji (--dir) i adres URL docelowej bazy danych (--url) do zastosowania plików migracji.