1. Instalacja narzędzia ent

Aby zainstalować narzędzie generowania kodu ent, należy postępować zgodnie z poniższymi krokami:

Po pierwsze, upewnij się, że Twój system ma zainstalowane środowisko języka Go. Następnie uruchom poniższą komendę, aby pobrać narzędzie ent:

go get -d entgo.io/ent/cmd/ent

Ta komenda pobierze kod narzędzia ent, ale nie skompiluje go i nie zainstaluje od razu. Jeśli chcesz zainstalować ent w katalogu $GOPATH/bin, aby móc go używać w dowolnym miejscu, musisz również wykonać poniższą komendę:

go install entgo.io/ent/cmd/ent

Po zakończeniu instalacji, możesz sprawdzić, czy narzędzie ent zostało poprawnie zainstalowane i wyświetlić dostępne polecenia i instrukcje, wykonując ent -h.

2. Inicjowanie schematu

2.1 Inicjowanie szablonu przy użyciu ent init

Utworzenie nowego pliku schematu to pierwszy krok rozpoczęcia korzystania z narzędzia ent do generowania kodu. Możesz zainicjować szablon schematu, wykonując poniższą komendę:

go run -mod=mod entgo.io/ent/cmd/ent new User Pet

Ta komenda utworzy dwa nowe pliki schematu: user.go i pet.go, i umieści je w katalogu ent/schema. Jeśli katalog ent nie istnieje, ta komenda również go automatycznie utworzy.

Wykonywanie polecenia ent init w głównym katalogu projektu jest dobrym zwyczajem, ponieważ pomaga utrzymać strukturę katalogu projektu czytelną i klarowną.

2.2 Struktura plików schematu

W katalogu ent/schema każdy schemat odpowiada plikowi źródłowemu języka Go. Pliki schematów to miejsce, w którym definiujesz model bazy danych, w tym pola i krawędzie (relacje).

Na przykład w pliku user.go możesz zdefiniować model użytkownika, zawierający takie pola jak nazwa użytkownika i wiek, oraz zdefiniować relację między użytkownikami a zwierzętami domowymi. Podobnie w pliku pet.go zdefiniowałbyś model zwierzęcia domowego i związane z nim pola, takie jak imię zwierzęcia, jego rodzaj oraz relację między zwierzętami a użytkownikami.

Te pliki będą ostatecznie wykorzystane przez narzędzie ent do generowania odpowiadającego kodu Go, w tym kodu klienta do operacji na bazie danych i operacji CRUD (Create, Read, Update, Delete).

// ent/schema/user.go
package schema

import (
    "entgo.io/ent"
    "entgo.io/ent/schema/field"
)

// User definiuje schemat dla rodzaju User.
type User struct {
    ent.Schema
}

// Metoda Fields służy do definiowania pól encji.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").Unique(),
        field.Int("age").Positive(),
    }
}

// Metoda Edges służy do definiowania powiązań encji.
func (User) Edges() []ent.Edge {
    // Dalsze informacje dotyczące powiązań zostaną wyjaśnione w następnym rozdziale.
}

Pliki schematów ent używają typów i funkcji języka Go do deklarowania struktury modelu bazy danych, w tym pól i relacji między modelami, oraz wykorzystują interfejs API dostarczany przez framework ent do definiowania tych struktur. Ten sposób pozwala na intuicyjne i łatwe rozszerzanie ent, wykorzystując jednocześnie silne typowanie języka Go.

3.1 Uruchamianie generowania kodu

Uruchomienie ent generate w celu wygenerowania kodu jest kluczowym krokiem w ramach frameworka ent. Za pomocą tego polecenia ent wygeneruje odpowiadające pliki kodu Go na podstawie zdefiniowanych schematów, ułatwiając następną pracę deweloperską. Polecenie wykonania generowania kodu jest proste:

go generate ./ent

Powyższa komenda musi być uruchomiona w głównym katalogu projektu. Gdy jest wywoływane go generate, wyszuka wszystkie pliki Go zawierające konkretne adnotacje i wykona polecenia określone w adnotacjach. W naszym przykładzie to polecenie określa generator kodu dla ent.

Upewnij się, że inicjalizacja schematu oraz konieczne dodanie pól zostały zakończone przed wykonaniem. Dopiero wtedy wygenerowany kod będzie zawierać wszystkie niezbędne części.

3.2 Zrozumienie wygenerowanych zasobów kodu

Wygenerowane zasoby kodu zawierają wiele komponentów, z różnymi funkcjami:

  • Obiekty Client i Tx: Służą do interakcji z grafem danych. Client udostępnia metody do tworzenia transakcji (Tx) lub bezpośredniego wykonywania operacji na bazie danych.

  • Budowniczowie CRUD: Dla każdego typu schematu generuje budownicze do tworzenia, czytania, aktualizacji i usuwania, upraszczając logikę operacji odpowiadającej encji.

  • Obiekt encji (Go struct): Generuje odpowiadające im Go struktury dla każdego typu w schemacie, mapując te struktury do tabel w bazie danych.

  • Pakiet stałych i predykatów: Zawiera stałe i predykaty do interakcji z budowniczymi.

  • Pakiet migracji: Pakiet do migrowania bazy danych, odpowiedni dla dialektów SQL.

  • Pakiet hook: Zapewnia funkcjonalność dodawania środków pośredniczących w zmianach, pozwalając na wykonanie niestandardowej logiki przed lub po tworzeniu, aktualizowaniu lub usuwaniu encji.

Analizując wygenerowany kod, można uzyskać głębsze zrozumienie sposobu, w jaki framework ent automatyzuje kod dostępu do danych dla Twoich schematów.

4. Opcje generowania kodu

Polecenie ent generate obsługuje różne opcje, pozwalające dostosować proces generowania kodu. Wszystkie obsługiwane opcje generowania można sprawdzić za pomocą poniższego polecenia:

ent generate -h

Oto kilka powszechnie używanych opcji wiersza poleceń:

  • --feature strings: Rozszerza generowanie kodu, dodając dodatkową funkcjonalność.
  • --header string: Zastępuje nagłówek generowanego pliku kodu.
  • --storage string: Określa sterownik magazynu obsługiwany podczas generowania kodu, domyślnie ustawiony na "sql".
  • --target string: Określa katalog docelowy dla generowania kodu.
  • --template strings: Wykonuje dodatkowe szablony Go. Obsługuje ścieżki plików, katalogów i znaków wieloznacznych, na przykład: --template file="ścieżka/do/pliku.tmpl".

Te opcje pozwalają programistom dostosować proces generowania kodu zgodnie z różnymi potrzebami i preferencjami.

5. Konfiguracja opcji magazynu

ent obsługuje generowanie zasobów kodu zarówno dla dialektów SQL, jak i Gremlin, gdzie domyślnie używany jest dialekt SQL. Jeśli projekt musi połączyć się z bazą danych Gremlin, konieczne jest skonfigurowanie odpowiedniego dialektu bazy danych. Poniższy przykład demonstruje, jak określić opcje magazynu:

ent generate --storage gremlin ./ent/schema

Powyższe polecenie instruuje ent, aby używał dialektu Gremlin podczas generowania kodu. Zapewnia to, że wygenerowane zasoby są dopasowane do wymagań bazy danych Gremlin, zapewniając kompatybilność z konkretną bazą danych grafową.

6. Zaawansowane użycie: Pakiet entc

6.1 Użycie entc jako pakietu w projekcie

entc to główny pakiet używany do generowania kodu w frameworku ent. Oprócz narzędzia wiersza poleceń, entc można również zintegrować w projekcie jako pakiet, pozwalając programistom kontrolować i dostosowywać proces generowania kodu bezpośrednio w samym kodzie.

Aby użyć entc jako pakietu w projekcie, należy utworzyć plik o nazwie entc.go i dodać do niego następującą zawartość:

// +build ignore

package main

import (
    "log"
    "entgo.io/ent/entc"
    "entgo.io/ent/entc/gen"
)

func main() {
    if err := entc.Generate("./schema", &gen.Config{}); err != nil {
        log.Fatal("running ent codegen:", err)
    }
}

Korzystając z tego podejścia, możesz modyfikować strukturę gen.Config wewnątrz funkcji main, aby zastosować różne opcje konfiguracji. Wywołując funkcję entc.Generate według potrzeb, możesz elastycznie kontrolować proces generowania kodu.

6.2 Szczegółowa konfiguracja entc

entc udostępnia obszerną gamę opcji konfiguracyjnych, pozwalając deweloperom dostosować generowany kod. Na przykład, można skonfigurować niestandardowe haki do inspekcji lub modyfikowania wygenerowanego kodu oraz wstrzykiwanie zewnętrznych zależności przy użyciu wstrzykiwania zależności.

Poniższy przykład demonstruje, jak dostarczyć niestandardowe haki dla funkcji entc.Generate:

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            HookFunction,
        },
    })
    if err != nil {
        log.Fatalf("błąd generowania kodu ent: %v", err)
    }
}

// HookFunction jest niestandardową funkcją haka
func HookFunction(next gen.Generator) gen.Generator {
    return gen.GenerateFunc(func(g *gen.Graph) error {
        // Tutaj można obsłużyć tryb grafu reprezentowany przez g
        // Na przykład, sprawdź istnienie pól lub zmodyfikuj strukturę
        return next.Generate(g)
    })
}

Dodatkowo, zewnętrzne zależności można dodać, używając entc.Dependency:

func main() {
    opts := []entc.Option{
        entc.Dependency(
            entc.DependencyType(&http.Client{}),
        ),
        entc.Dependency(
            entc.DependencyName("Writer"),
            entc.DependencyTypeInfo(&field.TypeInfo{
                Ident:   "io.Writer",
                PkgPath: "io",
            }),
        ),
    }
    if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
        log.Fatalf("błąd generowania kodu ent: %v", err)
    }
}

W tym przykładzie wstrzykujemy http.Client i io.Writer jako zależności do wygenerowanych obiektów klientów.

7. Wyjście opisu schematu

W frameworku ent, polecenie ent describe może być użyte do wyprowadzenia opisu schematu w formie graficznej. Pomaga to deweloperom szybko zrozumieć istniejące encje i relacje.

Aby uzyskać opis schematu, wykonaj następujące polecenie:

go run -mod=mod entgo.io/ent/cmd/ent describe ./ent/schema

Powyższe polecenie wyprowadzi tabelę podobną do poniższej, wyświetlającą informacje takie jak pola, typy, relacje, itp. dla każdej encji:

User:
    +-------+---------+--------+-----------+ ...
    | Pole  |  Typ    | Unikalne | Opcjonalne  | ...
    +-------+---------+--------+-----------+ ...
    | id    | int     | False  | False     | ...
    | name  | string  | True   | False     | ...
    +-------+---------+--------+-----------+ ...
    +-------+--------+---------+-----------+ ...
    | Krawędź  |  Typ  | Odwrotna | Relacja  | ...
    +-------+--------+---------+-----------+ ...
    | pets  | Pet    | False   | O2M       | ...
    +-------+--------+---------+-----------+ ...

8. Haki generacji kodu

8.1 Pojęcie haków

Haki są funkcjami pośredniczącymi, które można wstawić do procesu generacji kodu ent, pozwalając na wstawienie niestandardowej logiki przed i po wygenerowaniu kodu. Haki można użyć do manipulacji drzewem składniowym generowanego kodu, wykonania walidacji lub dodania dodatkowych fragmentów kodu.

8.2 Przykład użycia haków

Oto przykład użycia haka, aby upewnić się, że wszystkie pola zawierają określony tag struktury (np. json):

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            EnsureStructTag("json"),
        },
    })
    if err != nil {
        log.Fatalf("błąd generowania kodu ent: %v", err)
    }
}

// EnsureStructTag upewnia się, że wszystkie pola w grafie zawierają określony tag struktury
func EnsureStructTag(name string) gen.Hook {
    return func(next gen.Generator) gen.Generator {
        return gen.GenerateFunc(func(g *gen.Graph) error {
            for _, node := range g.Nodes {
                for _, field := range node.Fields {
                    tag := reflect.StructTag(field.StructTag)
                    if _, ok := tag.Lookup(name); !ok {
                        return fmt.Errorf("brak tagu struktury %q dla pola %s.%s", name, node.Name, field.Name)
                    }
                }
            }
            return next.Generate(g)
        })
    }
}

W tym przykładzie przed wygenerowaniem kodu funkcja EnsureStructTag sprawdza każde pole pod kątem tagu json. Jeśli pole nie zawiera tego tagu, generacja kodu zostanie przerwana i zwrócony zostanie błąd. Jest to skuteczny sposób utrzymania porządku i spójności kodu.