1. Installazione dello strumento ent

Per installare lo strumento di generazione di codice ent, è necessario seguire questi passaggi:

Innanzitutto, assicurati che il tuo sistema abbia l'ambiente di linguaggio Go installato. Quindi, esegui il seguente comando per ottenere lo strumento ent:

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

Questo comando scaricherà il codice per lo strumento ent, ma non lo compilerà e installerà immediatamente. Se desideri installare ent nella directory $GOPATH/bin in modo da poterlo utilizzare ovunque, è anche necessario eseguire il seguente comando:

go install entgo.io/ent/cmd/ent

Una volta completata l'installazione, puoi verificare se lo strumento ent è stato installato correttamente e visualizzare i comandi disponibili e le istruzioni eseguendo ent -h.

2. Inizializzazione dello Schema

2.1 Inizializzazione del Modello Utilizzando ent init

Creare un nuovo file di schema è il primo passo per iniziare a utilizzare ent per la generazione di codice. È possibile inizializzare il modello dello schema eseguendo il seguente comando:

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

Questo comando creerà due nuovi file di schema: user.go e pet.go, e li posizionerà nella directory ent/schema. Se la directory ent non esiste, questo comando la creerà automaticamente.

Eseguire il comando ent init nella directory radice del progetto è una pratica consigliata, poiché aiuta a mantenere la struttura e la chiarezza della directory del progetto.

2.2 Struttura del File di Schema

Nella directory ent/schema, ogni schema corrisponde a un file di origine del linguaggio Go. I file di schema sono dove si definisce il modello del database, inclusi i campi e gli edges (relazioni).

Ad esempio, nel file user.go, potresti definire un modello utente, inclusi campi come nome utente ed età, e definire la relazione tra utenti e animali domestici. Allo stesso modo, nel file pet.go, definiresti il modello dell'animale domestico e i suoi campi correlati, come il nome dell'animale, il tipo e la relazione tra animali domestici e utenti.

Questi file saranno infine utilizzati dallo strumento ent per generare il corrispondente codice Go, inclusa la parte client per le operazioni sul database e le operazioni CRUD (Create, Read, Update, Delete).

// ent/schema/user.go
package schema

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

// User definisce lo schema per l'entità Utente.
type User struct {
    ent.Schema
}

// Il metodo Fields viene utilizzato per definire i campi dell'entità.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("nome").Unique(),
        field.Int("età").Positive(),
    }
}

// Il metodo Edges viene utilizzato per definire le associazioni dell'entità.
func (User) Edges() []ent.Edge {
    // Le associazioni saranno spiegate in dettaglio nella sezione successiva.
}

I file di schema di ent utilizzano tipi e funzioni del linguaggio Go per dichiarare la struttura del modello del database, inclusi i campi e le relazioni tra i modelli, e utilizzano l'API fornita dal framework ent per definire queste strutture. Questo approccio rende ent molto intuitivo e facile da estendere, sfruttando al contempo la strong typing del linguaggio Go.

3.1 Esecuzione della Generazione del Codice

Eseguire ent generate per generare il codice è un passaggio cruciale nel framework ent. Con questo comando, ent genererà i file di codice Go corrispondenti in base agli schemi definiti, facilitando il lavoro di sviluppo successivo. Il comando per eseguire la generazione del codice è semplice:

go generate ./ent

Il comando sopra indicato deve essere eseguito nella directory radice del progetto. Quando viene chiamato go generate, verranno cercati tutti i file Go che contengono specifiche annotazioni e verranno eseguiti i comandi specificati nelle annotazioni. Nel nostro esempio, questo comando specifica il generatore di codice per ent.

Assicurati che l'inizializzazione dello schema e le aggiunte di campi necessarie siano state completate prima dell'esecuzione. Solo in questo modo il codice generato includerà tutte le parti necessarie.

3.2 Comprensione dei Asset di Codice Generati

Gli asset di codice generati contengono diversi componenti, ognuno con funzioni diverse:

  • Oggetti Client e Tx: Utilizzati per interagire con il grafo dei dati. Il Client fornisce metodi per creare transazioni (Tx) o eseguire direttamente operazioni sul database.

  • Generatore CRUD: Per ogni tipo di schema, genera generatori per creare, leggere, aggiornare ed eliminare, semplificando la logica di operazione dell'entità corrispondente.

  • Oggetto Entità (struct Go): Genera le struct Go corrispondenti per ciascun tipo nello schema, mappando queste struct alle tabelle nel database.

  • Costanti e pacchetto dei predicati: Contiene costanti e predicati per interagire con i generatori.

  • Pacchetto Migrate: Un pacchetto per la migrazione del database, adatto ai dialetti SQL.

  • Pacchetto Hook: Fornisce la funzionalità per aggiungere middleware di modifica, consentendo l'esecuzione di logica personalizzata prima o dopo la creazione, l'aggiornamento o l'eliminazione delle entità.

Esaminando il codice generato, è possibile acquisire una comprensione più approfondita di come il framework ent automatizzi il codice di accesso ai dati per i tuoi schemi.

4. Opzioni di Generazione del Codice

Il comando ent generate supporta varie opzioni per personalizzare il processo di generazione del codice. È possibile controllare tutte le opzioni di generazione supportate tramite il seguente comando:

ent generate -h

Ecco alcune opzioni della riga di comando comunemente utilizzate:

  • --feature strings: Estende la generazione del codice, aggiungendo funzionalità aggiuntive.
  • --header string: Sostituisce il file dell'intestazione di generazione del codice.
  • --storage string: Specifica il driver di storage supportato nella generazione del codice, predefinito su "sql".
  • --target string: Specifica la directory di destinazione per la generazione del codice.
  • --template strings: Esegue modelli Go aggiuntivi. Supporta file, directory e percorsi con caratteri jolly, ad esempio: --template file="percorso/al/file.tmpl".

Queste opzioni consentono ai developer di personalizzare il processo di generazione del codice in base a diverse esigenze e preferenze.

5. Configurazione delle Opzioni di Storage

ent supporta la generazione di asset di codice sia per i dialetti SQL che per i dialetti Gremlin, con il dialetto predefinito che è SQL. Se il progetto ha bisogno di connettersi a un database Gremlin, è necessario configurare il dialetto del database corrispondente. Di seguito viene mostrato come specificare le opzioni di storage:

ent generate --storage gremlin ./ent/schema

Il comando sopra indica a ent di utilizzare il dialetto Gremlin durante la generazione del codice. Ciò assicura che gli asset generati siano personalizzati per i requisiti del database Gremlin, garantendo la compatibilità con un database grafico specifico.

6. Utilizzo Avanzato: Pacchetto entc

6.1 Utilizzo di entc come Pacchetto nel Progetto

entc è il pacchetto principale utilizzato per la generazione del codice nel framework ent. Oltre allo strumento della riga di comando, entc può essere integrato nel progetto come pacchetto, consentendo ai developer di controllare e personalizzare il processo di generazione del codice all'interno del codice stesso.

Per utilizzare entc come pacchetto nel progetto, è necessario creare un file chiamato entc.go e aggiungere il seguente contenuto al file:

// +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("esecuzione di ent codegen:", err)
    }
}

Utilizzando questo approccio, è possibile modificare la struct gen.Config all'interno della funzione main per applicare diverse opzioni di configurazione. Chiamando la funzione entc.Generate secondo necessità, è possibile controllare in modo flessibile il processo di generazione del codice.

6.2 Configurazione dettagliata di entc

entc fornisce ampie opzioni di configurazione, consentendo agli sviluppatori di personalizzare il codice generato. Ad esempio, è possibile configurare hook personalizzati per ispezionare o modificare il codice generato, nonché iniettare dipendenze esterne utilizzando l'iniezione delle dipendenze.

Nell'esempio seguente viene mostrato come fornire hook personalizzati per la funzione entc.Generate:

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            HookFunction,
        },
    })
    if err != nil {
        log.Fatalf("esecuzione del codegen di ent: %v", err)
    }
}

// HookFunction è una funzione hook personalizzata
func HookFunction(next gen.Generator) gen.Generator {
    return gen.GenerateFunc(func(g *gen.Graph) error {
        // È possibile gestire qui la modalità di grafo rappresentata da g
        // Ad esempio, convalidare l'esistenza dei campi o modificare la struttura
        return next.Generate(g)
    })
}

Inoltre, le dipendenze esterne possono essere aggiunte utilizzando 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("esecuzione del codegen di ent: %v", err)
    }
}

In questo esempio, vengono iniettate http.Client e io.Writer come dipendenze negli oggetti client generati.

7. Descrizione dello schema in output

Nel framework ent, il comando ent describe può essere utilizzato per visualizzare la descrizione dello schema in un formato grafico. Ciò può aiutare gli sviluppatori a comprendere rapidamente le entità e le relazioni esistenti.

Eseguire il seguente comando per ottenere la descrizione dello schema:

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

Il comando sopra mostrerà una tabella simile alla seguente, visualizzando informazioni come campi, tipi, relazioni, etc. per ciascuna entità:

Utente:
    +-------+---------+--------+-----------+ ...
    | Campo |  Tipo   | Unico  | Opzionale | ...
    +-------+---------+--------+-----------+ ...
    | id    | int     | falso  | falso     | ...
    | nome  | string  | vero   | falso     | ...
    +-------+---------+--------+-----------+ ...
    +-------+--------+---------+-----------+ ...
    | Tratto|  Tipo  | Inverso | Relazione | ...
    +-------+--------+---------+-----------+ ...
    | animali| Animale | falso   | O2M       | ...
    +-------+--------+---------+-----------+ ...

8. Hook di generazione del codice

8.1 Concetto degli Hook

Gli hook sono funzioni middleware che possono essere inserite nel processo di generazione del codice ent, consentendo l'inserimento di logica personalizzata prima e dopo la generazione del codice. Gli hook possono essere utilizzati per manipolare l'albero della sintassi astratta (AST) del codice generato, eseguire la convalida o aggiungere frammenti di codice aggiuntivi.

8.2 Esempio di utilizzo degli Hook

Ecco un esempio di utilizzo di un hook per garantire che tutti i campi contengano un determinato tag di struttura (ad esempio, json):

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            EnsureStructTag("json"),
        },
    })
    if err != nil {
        log.Fatalf("esecuzione del codegen di ent: %v", err)
    }
}

// EnsureStructTag garantisce che tutti i campi nel grafo contengano un tag di struttura specifico
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("manca il tag di struttura %q per il campo %s.%s", name, node.Name, field.Name)
                    }
                }
            }
            return next.Generate(g)
        })
    }
}

In questo esempio, prima di generare il codice, la funzione EnsureStructTag controlla ciascun campo per il tag json. Se un campo manca di questo tag, la generazione del codice terminerà e restituirà un errore. Si tratta di un modo efficace per mantenere la pulizia e la coerenza del codice.