1. Einführung in ent
Ent ist ein von Facebook speziell für die Go-Sprache entwickeltes Entitätsframework. Es vereinfacht den Prozess des Erstellens und der Wartung von datenmodellierten Anwendungen im großen Maßstab. Das Ent-Framework folgt im Wesentlichen den folgenden Prinzipien:
- Die Datenbankschemata lassen sich leicht als Graphenstruktur modellieren.
- Das Schema wird in Form von Go-Sprachcode definiert.
- Implementierung von statischen Typen basierend auf Codegenerierung.
- Das Schreiben von Datenbankabfragen und Graphentraversierungen ist sehr einfach.
- Leicht erweiterbar und anpassbar unter Verwendung von Go-Vorlagen.
2. Einrichten der Umgebung
Um das Ent-Framework zu nutzen, stellen Sie sicher, dass die Go-Sprache in Ihrer Entwicklungsumgebung installiert ist.
Wenn sich Ihr Projektverzeichnis außerhalb von GOPATH
befindet oder wenn Sie nicht mit GOPATH
vertraut sind, können Sie den folgenden Befehl verwenden, um ein neues Go-Modulprojekt zu erstellen:
go mod init entdemo
Damit wird ein neues Go-Modul initialisiert und eine neue go.mod
-Datei für Ihr entdemo
-Projekt erstellt.
3. Definition des ersten Schemas
3.1. Erstellen des Schemas mit dem ent CLI
Zuerst müssen Sie den folgenden Befehl im Stammverzeichnis Ihres Projekts ausführen, um ein Schema mit dem Namen "User" mithilfe des ent CLI-Tools zu erstellen:
go run -mod=mod entgo.io/ent/cmd/ent new User
Der obige Befehl generiert das Benutzer-Schema im Verzeichnis entdemo/ent/schema/
:
Datei entdemo/ent/schema/user.go
:
package schema
import "entgo.io/ent"
// User hält die Schema-Definition für die Benutzer-Entität.
type User struct {
ent.Schema
}
// Felder des Benutzers.
func (User) Fields() []ent.Field {
return nil
}
// Kanten des Benutzers.
func (User) Edges() []ent.Edge {
return nil
}
3.2. Hinzufügen von Feldern
Als nächstes müssen Felddefinitionen zum Benutzer-Schema hinzugefügt werden. Im Folgenden finden Sie ein Beispiel zum Hinzufügen von zwei Feldern zur Benutzer-Entität.
Geänderte Datei entdemo/ent/schema/user.go
:
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// Felder des Benutzers.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age").
Positive(),
field.String("name").
Default("unknown"),
}
}
Dieser Code definiert zwei Felder für das Benutzermodell: age
und name
, wobei age
eine positive Ganzzahl und name
ein String mit dem Standardwert "unknown" ist.
3.3. Generierung von Datenbankentitäten
Nachdem das Schema definiert ist, müssen Sie den Befehl go generate
ausführen, um die zugrunde liegende Datenbankzugriffslogik zu generieren.
Führen Sie den folgenden Befehl im Stammverzeichnis Ihres Projekts aus:
go generate ./ent
Dieser Befehl generiert den entsprechenden Go-Code anhand des zuvor definierten Schemas und führt zu der folgenden Dateistruktur:
ent
├── client.go
├── config.go
├── context.go
├── ent.go
├── generate.go
├── mutation.go
... (mehrere Dateien aus Platzgründen weggelassen)
├── schema
│ └── user.go
├── tx.go
├── user
│ ├── user.go
│ └── where.go
├── user.go
├── user_create.go
├── user_delete.go
├── user_query.go
└── user_update.go
4.1. Datenbankverbindung initialisieren
Um eine Verbindung zur MySQL-Datenbank herzustellen, können wir die durch das ent
-Framework bereitgestellte Open
-Funktion verwenden. Importieren Sie zunächst den MySQL-Treiber und geben Sie dann den korrekten Verbindungsstring ein, um die Datenbankverbindung zu initialisieren.
package main
import (
"context"
"log"
"entdemo/ent"
_ "github.com/go-sql-driver/mysql" // Importieren des MySQL-Treibers
)
func main() {
// Verwenden Sie ent.Open, um eine Verbindung mit der MySQL-Datenbank herzustellen.
// Ersetzen Sie die Platzhalter "dein_benutzername", "dein_passwort" und "deine_datenbank" unten.
client, err := ent.Open("mysql", "dein_benutzername:dein_passwort@tcp(localhost:3306)/deine_datenbank?parseTime=True")
if err != nil {
log.Fatalf("Fehler beim Öffnen der Verbindung zu MySQL: %v", err)
}
defer client.Close()
// Führen Sie das automatische Migrationswerkzeug aus
ctx := context.Background()
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("Fehler beim Erstellen der Schema-Ressourcen: %v", err)
}
// Hier können zusätzliche Geschäftslogik geschrieben werden
}
4.2. Entitäten erstellen
Die Erstellung einer Benutzerentität beinhaltet den Aufbau eines neuen Entitätsobjekts und das Speichern in der Datenbank mit der Methode Save
oder SaveX
. Der folgende Code zeigt, wie eine neue Benutzerentität erstellt und zwei Felder age
und name
initialisiert werden.
// Die Funktion CreateUser wird verwendet, um eine neue Benutzerentität zu erstellen
func CreateUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
// Verwenden von client.User.Create(), um die Anfrage zum Erstellen eines Benutzers zu erstellen,
// dann die Methoden SetAge und SetName verketten, um die Werte der Entitätsfelder festzulegen.
u, err := client.User.
Create().
SetAge(30). // Benutzeralter festlegen
SetName("a8m"). // Benutzernamen festlegen
Save(ctx) // Aufrufen von Save, um die Entität in die Datenbank zu speichern
if err != nil {
return nil, fmt.Errorf("Fehler beim Erstellen des Benutzers: %w", err)
}
log.Println("Benutzer wurde erstellt: ", u)
return u, nil
}
In der main
-Funktion können Sie die CreateUser
-Funktion aufrufen, um eine neue Benutzerentität zu erstellen.
func main() {
// ...Ausgelassener Code zur Herstellung der Datenbankverbindung
// Erstellen einer Benutzerentität
u, err := CreateUser(ctx, client)
if err != nil {
log.Fatalf("Fehler beim Erstellen des Benutzers: %v", err)
}
log.Printf("Benutzer erstellt: %#v\n", u)
}
4.3. Abfragen von Entitäten
Um Entitäten abzufragen, können wir den durch ent
generierten Abfrage-Builder verwenden. Der folgende Code zeigt, wie man nach einem Benutzer mit dem Namen "a8m" sucht:
// Die Funktion QueryUser wird verwendet, um die Benutzerentität mit einem angegebenen Namen abzufragen
func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
// Verwenden von client.User.Query(), um die Abfrage für Benutzer zu erstellen,
// dann verketten Sie die Where-Methode, um Abfragebedingungen hinzuzufügen, wie z.B. Abfragen nach Benutzernamen
u, err := client.User.
Query().
Where(user.NameEQ("a8m")). // Abfragebedingung hinzufügen, in diesem Fall ist der Name "a8m"
Only(ctx) // Die Only-Methode gibt an, dass nur ein Ergebnis erwartet wird
if err != nil {
return nil, fmt.Errorf("Fehler beim Abfragen des Benutzers: %w", err)
}
log.Println("Benutzer zurückgegeben: ", u)
return u, nil
}
In der main
-Funktion können Sie die QueryUser
-Funktion aufrufen, um die Benutzerentität abzufragen.
func main() {
// ...Ausgelassener Code zur Herstellung der Datenbankverbindung und Erstellung des Benutzers
// Abfragen der Benutzerentität
u, err := QueryUser(ctx, client)
if err != nil {
log.Fatalf("Fehler beim Abfragen des Benutzers: %v", err)
}
log.Printf("Abgefragter Benutzer: %#v\n", u)
}
5.1. Verstehen von Kanten und Umgekehrten Kanten
Im ent
-Framework wird das Datenmodell als Graphstruktur visualisiert, in der Entitäten die Knoten im Graphen darstellen und die Beziehungen zwischen den Entitäten durch Kanten dargestellt werden. Eine Kante ist eine Verbindung von einer Entität zu einer anderen, zum Beispiel kann ein Benutzer
mehrere Autos
besitzen.
Umgekehrte Kanten sind umgekehrte Verweise auf Kanten, die logischerweise die umgekehrte Beziehung zwischen Entitäten darstellen, jedoch keine neue Beziehung in der Datenbank erstellen. Durch die umgekehrte Kante eines Autos
können wir beispielsweise den Benutzer
finden, dem dieses Auto gehört.
Die Hauptbedeutung von Kanten und umgekehrten Kanten liegt darin, die Navigation zwischen zugehörigen Entitäten sehr intuitiv und unkompliziert zu gestalten.
Tipp: In
ent
entsprechen Kanten den herkömmlichen Fremdschlüsseln in Datenbanken und werden verwendet, um Beziehungen zwischen Tabellen zu definieren.
5.2. Definition von Kanten im Schema
Zunächst verwenden wir das ent
-CLI, um das initiale Schema für Auto
und Gruppe
zu erstellen:
go run -mod=mod entgo.io/ent/cmd/ent new Car Group
Anschließend definieren wir im Benutzer
-Schema die Kante mit Auto
, um die Beziehung zwischen Benutzern und Autos darzustellen. Wir können eine Kante cars
hinzufügen, die auf den Typ Car
in der Benutzerentität zeigt und somit angibt, dass ein Benutzer mehrere Autos haben kann:
// entdemo/ent/schema/user.go
// Kanten des Benutzers.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("cars", Car.Type),
}
}
Nachdem die Kanten definiert wurden, müssen wir erneut go generate ./ent
ausführen, um den entsprechenden Code zu generieren.
5.3. Arbeiten mit Kantendaten
Das Erstellen von Autos, die mit einem Benutzer in Verbindung stehen, ist ein einfacher Prozess. Mit einer Benutzerentität können wir eine neue Autoentität erstellen und mit dem Benutzer verknüpfen:
import (
"context"
"log"
"entdemo/ent"
// Stellen Sie sicher, dass die Schema-Definition für Car importiert wird
_ "entdemo/ent/schema"
)
func CreateCarsForUser(ctx context.Context, client *ent.Client, userID int) error {
user, err := client.User.Get(ctx, userID)
if err != nil {
log.Fatalf("Benutzerabruf fehlgeschlagen: %v", err)
return err
}
// Ein neues Auto erstellen und mit dem Benutzer verknüpfen
_, err = client.Car.
Create().
SetModel("Tesla").
SetRegisteredAt(time.Now()).
SetOwner(user).
Save(ctx)
if err != nil {
log.Fatalf("Fehler beim Erstellen des Autos für den Benutzer: %v", err)
return err
}
log.Println("Das Auto wurde erstellt und mit dem Benutzer verknüpft")
return nil
}
Das Abfragen der Autos eines Benutzers ist ebenso unkompliziert. Wenn wir eine Liste aller Autos, die ein Benutzer besitzt, abrufen möchten, können wir folgendes tun:
func QueryUserCars(ctx context.Context, client *ent.Client, userID int) error {
user, err := client.User.Get(ctx, userID)
if err != nil {
log.Fatalf("Benutzerabruf fehlgeschlagen: %v", err)
return err
}
// Alle Autos des Benutzers abfragen
cars, err := user.QueryCars().All(ctx)
if err != nil {
log.Fatalf("Fehler beim Abfragen der Autos: %v", err)
return err
}
for _, car := range cars {
log.Printf("Auto: %v, Modell: %v", car.ID, car.Model)
}
return nil
}
Durch die oben genannten Schritte haben wir nicht nur gelernt, wie man Kanten im Schema definiert, sondern auch demonstriert, wie man Daten im Zusammenhang mit Kanten erstellt und abfragt.
6. Traversieren und Abfragen von Graphen
6.1. Verständnis von Graphstrukturen
In ent
werden Graphstrukturen durch Entitäten und die Kanten zwischen ihnen dargestellt. Jede Entität entspricht einem Knoten im Graphen, und die Beziehungen zwischen den Entitäten werden durch Kanten dargestellt, die eins-zu-eins, eins-zu-viele, viele-zu-viele usw. sein können. Diese Graphstruktur macht komplexe Abfragen und Operationen in einer relationalen Datenbank einfach und intuitiv.
6.2. Graphenstrukturen durchlaufen
Die Erstellung von Code für die Traversierung von Graphen beinhaltet hauptsächlich die Abfrage und Verknüpfung von Daten über die Kanten zwischen Entitäten. Nachfolgend finden Sie ein einfaches Beispiel, das zeigt, wie die Traversierung der Graphenstruktur in ent
erfolgt:
import (
"context"
"log"
"entdemo/ent"
)
// GraphTraversal ist ein Beispiel zum Durchlaufen der Graphenstruktur
func GraphTraversal(ctx context.Context, client *ent.Client) error {
// Abfrage des Benutzers mit Namen "Ariel"
a8m, err := client.User.Query().Where(user.NameEQ("Ariel")).Only(ctx)
if err != nil {
log.Fatalf("Benutzerabfrage fehlgeschlagen: %v", err)
return err
}
// Durchlaufen aller Autos, die zu Ariel gehören
cars, err := a8m.QueryCars().All(ctx)
if err != nil {
log.Fatalf("Fehler bei der Abfrage von Autos: %v", err)
return err
}
for _, car := range cars {
log.Printf("Ariel hat ein Auto mit dem Modell: %s", car.Model)
}
// Durchlaufen aller Gruppen, in denen Ariel Mitglied ist
groups, err := a8m.QueryGroups().All(ctx)
if err != nil {
log.Fatalf("Fehler bei der Abfrage von Gruppen: %v", err)
return err
}
for _, g := range groups {
log.Printf("Ariel ist Mitglied der Gruppe: %s", g.Name)
}
return nil
}
Der obige Code ist ein einfaches Beispiel für die Traversierung von Graphen, das zunächst einen Benutzer abfragt und danach die Autos und Gruppen des Benutzers durchläuft.
7. Datenbankschema visualisieren
7.1. Installation des Atlas-Tools
Um das von ent
generierte Datenbankschema zu visualisieren, kann das Atlas-Tool verwendet werden. Die Installationsschritte für Atlas sind sehr einfach. Zum Beispiel können Sie es auf macOS mithilfe von brew
installieren:
brew install ariga/tap/atlas
Hinweis: Atlas ist ein universelles Datenbank-Migrationswerkzeug, das die Versionen der Tabellenstruktur für verschiedene Datenbanken verwalten kann. Eine ausführliche Einführung in Atlas wird in späteren Kapiteln gegeben.
7.2. ERD und SQL-Schema generieren
Die Verwendung von Atlas zur Anzeige und zum Export von Schemata ist sehr unkompliziert. Nach der Installation von Atlas können Sie den folgenden Befehl verwenden, um das Entity-Relationship-Diagramm (ERD) anzuzeigen:
atlas schema inspect -d [database_dsn] --format dot
Oder direkt das SQL-Schema generieren:
atlas schema inspect -d [database_dsn] --format sql
Dabei zeigt [database_dsn]
auf den Datenquellennamen (DSN) Ihrer Datenbank. Zum Beispiel für eine SQLite-Datenbank könnte es sein:
atlas schema inspect -d "sqlite://file:ent.db?mode=memory&cache=shared" --format dot
Die Ausgabe dieser Befehle kann mit entsprechenden Tools weiter zu Ansichten oder Dokumenten transformiert werden.
8. Schemamigration
8.1. Automatische Migration und Versionierte Migration
ent unterstützt zwei Strategien für die Schemamigration: automatische Migration und versionierte Migration. Die automatische Migration beinhaltet den Prozess der Inspektion und Anwendung von Schemänderungen zur Laufzeit, was für die Entwicklung und das Testen geeignet ist. Die versionierte Migration erfordert die Generierung von Migrations-Skripten und erfordert eine sorgfältige Prüfung und Tests vor dem Produktionsdeployment.
Tipp: Für die automatische Migration siehe den Inhalt in Abschnitt 4.1.
8.2. Durchführung der versionierten Migration
Der Prozess der versionierten Migration beinhaltet das Generieren von Migrationsdateien durch Atlas. Im Folgenden finden Sie die relevanten Befehle:
Um Migrationsdateien zu generieren:
atlas migrate diff -d ent/schema/path --dir migrations/dir
Anschließend können diese Migrationsdateien auf die Datenbank angewendet werden:
atlas migrate apply -d migrations/dir --url database_dsn
Durch diesen Prozess können Sie eine Historie der Datenbankmigrationen im Versionskontrollsystem führen und vor jeder Migration eine gründliche Prüfung sicherstellen.
Tipp: Siehe den Beispielscode unter https://github.com/ent/ent/tree/master/examples/start