1. Introduction à ent
Ent est un framework d'entité développé par Facebook spécifiquement pour le langage Go. Il simplifie le processus de construction et de maintenance d'applications de modèles de données à grande échelle. Le framework ent suit principalement les principes suivants :
- Modéliser facilement le schéma de la base de données sous forme d'une structure de graphique.
- Définir le schéma sous forme de code dans le langage Go.
- Implémenter des types statiques basés sur la génération de code.
- Écrire des requêtes de base de données et des traversées de graphiques est très simple.
- Facile à étendre et à personnaliser à l'aide de modèles Go.
2. Configuration de l'environnement
Pour commencer à utiliser le framework ent, assurez-vous que le langage Go est installé dans votre environnement de développement.
Si votre répertoire de projet est en dehors de GOPATH
, ou si vous n'êtes pas familier avec GOPATH
, vous pouvez utiliser la commande suivante pour créer un nouveau projet de module Go :
go mod init entdemo
Cela initialisera un nouveau module Go et créera un nouveau fichier go.mod
pour votre projet entdemo
.
3. Définition du premier schéma
3.1. Création du schéma à l'aide de l'outil ent CLI
Tout d'abord, vous devez exécuter la commande suivante dans le répertoire racine de votre projet pour créer un schéma nommé Utilisateur en utilisant l'outil ent CLI :
go run -mod=mod entgo.io/ent/cmd/ent new User
La commande ci-dessus générera le schéma Utilisateur dans le répertoire entdemo/ent/schema/
:
Fichier entdemo/ent/schema/user.go
:
package schema
import "entgo.io/ent"
// User détient la définition du schéma pour l'entité Utilisateur.
type User struct {
ent.Schema
}
// Champs de l'utilisateur.
func (User) Fields() []ent.Field {
return nil
}
// Bords de l'utilisateur.
func (User) Edges() []ent.Edge {
return nil
}
3.2. Ajout de champs
Ensuite, nous devons ajouter des définitions de champs au schéma Utilisateur. Voici un exemple d'ajout de deux champs à l'entité Utilisateur.
Fichier modifié entdemo/ent/schema/user.go
:
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// Champs de l'utilisateur.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age").
Positive(),
field.String("name").
Default("inconnu"),
}
}
Ce morceau de code définit deux champs pour le modèle Utilisateur : age
et name
, où age
est un entier positif et name
est une chaîne de caractères avec une valeur par défaut de "inconnu".
3.3. Générer des entités de base de données
Après avoir défini le schéma, vous devez exécuter la commande go generate
pour générer la logique d'accès à la base de données sous-jacente.
Exécutez la commande suivante dans le répertoire racine de votre projet :
go generate ./ent
Cette commande générera le code Go correspondant en fonction du schéma précédemment défini, ce qui entraînera la structure de fichiers suivante :
ent
├── client.go
├── config.go
├── context.go
├── ent.go
├── generate.go
├── mutation.go
... (plusieurs fichiers omis pour des raisons de concision)
├── 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. Initialisation de la connexion à la base de données
Pour établir une connexion à la base de données MySQL, nous pouvons utiliser la fonction Open
fournie par le framework ent
. Tout d'abord, importez le pilote MySQL, puis fournissez la chaîne de connexion correcte pour initialiser la connexion à la base de données.
package main
import (
"context"
"log"
"entdemo/ent"
_ "github.com/go-sql-driver/mysql" // Importez le pilote MySQL
)
func main() {
// Utilisez ent.Open pour établir une connexion avec la base de données MySQL.
// N'oubliez pas de remplacer les espaces réservés "votre_nom_utilisateur", "votre_mot_de_passe" et "votre_base_de_données" ci-dessous.
client, err := ent.Open("mysql", "votre_nom_utilisateur:votre_mot_de_passe@tcp(localhost:3306)/votre_base_de_données?parseTime=True")
if err != nil {
log.Fatalf("échec de l'ouverture de la connexion à MySQL : %v", err)
}
defer client.Close()
// Exécutez l'outil de migration automatique
ctx := context.Background()
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("échec de la création des ressources de schéma : %v", err)
}
// Des logiques métier supplémentaires peuvent être rédigées ici
}
4.2. Création d'entités
Pour créer une entité Utilisateur, il faut construire un nouvel objet entité et le persister dans la base de données en utilisant la méthode Save
ou SaveX
. Le code suivant démontre comment créer une nouvelle entité Utilisateur et initialiser deux champs age
et name
.
// La fonction CreateUser est utilisée pour créer une nouvelle entité Utilisateur
func CreateUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
// Utilisez client.User.Create() pour construire la requête de création d'un Utilisateur,
// puis enchaînez les méthodes SetAge et SetName pour définir les valeurs des champs de l'entité.
u, err := client.User.
Create().
SetAge(30). // Définissez l'âge de l'utilisateur
SetName("a8m"). // Définissez le nom de l'utilisateur
Save(ctx) // Appelez Save pour enregistrer l'entité dans la base de données
if err != nil {
return nil, fmt.Errorf("échec de la création de l'utilisateur : %w", err)
}
log.Println("utilisateur créé : ", u)
return u, nil
}
Dans la fonction main
, vous pouvez appeler la fonction CreateUser
pour créer une nouvelle entité utilisateur.
func main() {
// ...Code d'établissement de la connexion à la base de données omis
// Créer une entité utilisateur
u, err := CreateUser(ctx, client)
if err != nil {
log.Fatalf("échec de la création de l'utilisateur : %v", err)
}
log.Printf("utilisateur créé : %#v\n", u)
}
4.3. Interrogation des entités
Pour interroger des entités, nous pouvons utiliser le constructeur de requêtes généré par ent
. Le code suivant démontre comment interroger un utilisateur nommé "a8m":
// La fonction QueryUser est utilisée pour interroger l'entité utilisateur avec un nom spécifié
func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
// Utilisez client.User.Query() pour construire la requête pour l'Utilisateur,
// puis enchaînez la méthode Where pour ajouter des conditions de requête, telles que la requête par nom d'utilisateur
u, err := client.User.
Query().
Where(user.NameEQ("a8m")). // Ajoutez une condition de requête, dans ce cas, le nom est "a8m"
Only(ctx) // La méthode Only indique qu'un seul résultat est attendu
if err != nil {
return nil, fmt.Errorf("échec de la requête de l'utilisateur : %w", err)
}
log.Println("utilisateur retourné : ", u)
return u, nil
}
Dans la fonction main
, vous pouvez appeler la fonction QueryUser
pour interroger l'entité utilisateur.
func main() {
// ...Code d'établissement de la connexion à la base de données et de création d'utilisateur omis
// Interroger l'entité utilisateur
u, err := QueryUser(ctx, client)
if err != nil {
log.Fatalf("échec de la requête de l'utilisateur : %v", err)
}
log.Printf("utilisateur interrogé : %#v\n", u)
}
5.1. Comprendre les arêtes et les arêtes inverses
Dans le cadre du framework ent
, le modèle de données est visualisé comme une structure de graphe, où les entités représentent les nœuds dans le graphe, et les relations entre les entités sont représentées par des arêtes. Une arête est une connexion d'une entité à une autre, par exemple, un Utilisateur
peut posséder plusieurs Voitures
.
Les arêtes inverses sont des références inverses aux arêtes, représentant logiquement la relation inverse entre les entités, sans créer pour autant une nouvelle relation dans la base de données. Par exemple, grâce à l'arête inverse d'une Voiture
, nous pouvons trouver l'Utilisateur
qui possède cette voiture.
La signification principale des arêtes et des arêtes inverses réside dans la facilitation de la navigation entre les entités associées, de manière intuitive et directe.
Astuce : Dans
ent
, les arêtes correspondent aux clés étrangères traditionnelles de la base de données et sont utilisées pour définir les relations entre les tables.
5.2. Définition des arêtes dans le schéma
Tout d'abord, nous utiliserons l'interface en ligne de commande (CLI) ent
pour créer le schéma initial pour les entités Voiture
et Groupe
:
go run -mod=mod entgo.io/ent/cmd/ent new Car Group
Ensuite, dans le schéma de l'entité Utilisateur
, nous définissons l'arête avec Voiture
pour représenter la relation entre les utilisateurs et les voitures. Nous pouvons ajouter une arête cars
pointant vers le type Car
dans l'entité utilisateur, ce qui indique qu'un utilisateur peut avoir plusieurs voitures :
// entdemo/ent/schema/user.go
// Arêtes de l'utilisateur.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("cars", Car.Type),
}
}
Après avoir défini les arêtes, nous devons à nouveau exécuter go generate ./ent
pour générer le code correspondant.
5.3. Manipulation des données liées aux arêtes
La création de voitures associées à un utilisateur est un processus simple. Étant donné une entité utilisateur, nous pouvons créer une nouvelle entité de voiture et l'associer à l'utilisateur :
import (
"context"
"log"
"entdemo/ent"
// Assurez-vous d'importer la définition du schéma pour Car
_ "entdemo/ent/schema"
)
func CreateCarsForUser(ctx context.Context, client *ent.Client, userID int) error {
utilisateur, err := client.User.Get(ctx, userID)
if err != nil {
log.Fatalf("échec de la récupération de l'utilisateur : %v", err)
return err
}
// Créer une nouvelle voiture et l'associer à l'utilisateur
_, err = client.Car.
Create().
SetModel("Tesla").
SetRegisteredAt(time.Now()).
SetOwner(utilisateur).
Save(ctx)
if err != nil {
log.Fatalf("échec de la création de la voiture pour l'utilisateur: %v", err)
return err
}
log.Println("la voiture a été créée et associée à l'utilisateur")
return nil
}
De même, la requête des voitures d'un utilisateur est simple. Si nous voulons récupérer la liste de toutes les voitures possédées par un utilisateur, nous pouvons faire ce qui suit :
func QueryUserCars(ctx context.Context, client *ent.Client, userID int) error {
utilisateur, err := client.User.Get(ctx, userID)
if err != nil {
log.Fatalf("échec de la récupération de l'utilisateur : %v", err)
return err
}
// Interroger toutes les voitures possédées par l'utilisateur
voitures, err := utilisateur.QueryCars().All(ctx)
if err != nil {
log.Fatalf("échec de l'interrogation des voitures : %v", err)
return err
}
for _, voiture := range voitures {
log.Printf("voiture : %v, modèle : %v", voiture.ID, voiture.Model)
}
return nil
}
Grâce aux étapes ci-dessus, nous avons non seulement appris à définir des arêtes dans le schéma, mais aussi démontré comment créer et interroger des données liées aux arêtes.
6. Traversée et interrogation du graphe
6.1. Comprendre les structures de graphe
Dans ent
, les structures de graphe sont représentées par des entités et les arêtes entre elles. Chaque entité est équivalente à un nœud dans le graphe, et les relations entre les entités sont représentées par des arêtes, qui peuvent être un à un, un à plusieurs, plusieurs à plusieurs, etc. Cette structure de graphe rend les requêtes et opérations complexes sur une base de données relationnelle simples et intuitives.
6.2. Parcours des structures de graphique
L'écriture du code de parcours de graphique implique principalement la consultation et l'association de données via les liens entre entités. Voici un exemple simple démontrant comment parcourir la structure de graphique dans ent
:
import (
"context"
"log"
"entdemo/ent"
)
// GraphTraversal est un exemple de parcours de la structure de graphique
func GraphTraversal(ctx context.Context, client *ent.Client) error {
// Interroger l'utilisateur nommé "Ariel"
a8m, err := client.User.Query().Where(user.NameEQ("Ariel")).Only(ctx)
if err != nil {
log.Fatalf("Échec de la requête de l'utilisateur : %v", err)
return err
}
// Parcourir toutes les voitures appartenant à Ariel
cars, err := a8m.QueryCars().All(ctx)
if err != nil {
log.Fatalf("Échec de la requête des voitures : %v", err)
return err
}
for _, car := range cars {
log.Printf("Ariel a une voiture avec le modèle : %s", car.Model)
}
// Parcourir tous les groupes dont Ariel est membre
groups, err := a8m.QueryGroups().All(ctx)
if err != nil {
log.Fatalf("Échec de la requête des groupes : %v", err)
return err
}
for _, g := range groups {
log.Printf("Ariel est membre du groupe : %s", g.Name)
}
return nil
}
Le code ci-dessus est un exemple basique de parcours de graphique, qui interroge d'abord un utilisateur, puis parcourt les voitures et les groupes de l'utilisateur.
7. Visualisation du schéma de la base de données
7.1. Installation de l'outil Atlas
Pour visualiser le schéma de la base de données généré par ent
, nous pouvons utiliser l'outil Atlas. Les étapes d'installation d'Atlas sont très simples. Par exemple, sur macOS, vous pouvez l'installer en utilisant brew
:
brew install ariga/tap/atlas
Remarque : Atlas est un outil universel de migration de base de données qui peut gérer la gestion des versions de la structure de table pour diverses bases de données. Une introduction détaillée à Atlas sera fournie dans les chapitres ultérieurs.
7.2. Génération du schéma ERD et SQL
Utiliser Atlas pour visualiser et exporter des schémas est très simple. Après l'installation d'Atlas, vous pouvez utiliser la commande suivante pour visualiser le diagramme entité-relation (ERD) :
atlas schema inspect -d [database_dsn] --format dot
Ou générer directement le schéma SQL :
atlas schema inspect -d [database_dsn] --format sql
Où [database_dsn]
pointe vers le nom de la source de données (DSN) de votre base de données. Par exemple, pour une base de données SQLite, cela pourrait être :
atlas schema inspect -d "sqlite://file:ent.db?mode=memory&cache=shared" --format dot
La sortie générée par ces commandes peut ensuite être transformée en vues ou documents à l'aide des outils respectifs.
8. Migration de schéma
8.1. Migration automatique et migration versionnée
ent prend en charge deux stratégies de migration de schéma : la migration automatique et la migration versionnée. La migration automatique consiste à inspecter et à appliquer les modifications de schéma à l'exécution, adaptée au développement et aux tests. La migration versionnée implique la génération de scripts de migration et nécessite une révision et des tests minutieux avant le déploiement en production.
Astuce : Pour la migration automatique, consultez le contenu de la section 4.1.
8.2. Exécution d'une migration versionnée
Le processus de migration versionnée implique la génération de fichiers de migration via Atlas. Voici les commandes pertinentes :
Pour générer des fichiers de migration :
atlas migrate diff -d ent/schema/path --dir migrations/dir
Ensuite, ces fichiers de migration peuvent être appliqués à la base de données :
atlas migrate apply -d migrations/dir --url database_dsn
Suite à ce processus, vous pouvez conserver un historique des migrations de base de données dans le système de contrôle de version et garantir une révision approfondie avant chaque migration.
Astuce : Consultez le code d'exemple sur https://github.com/ent/ent/tree/master/examples/start