1. Notions sur les modèles et les champs
1.1. Introduction à la définition du modèle
Dans un framework ORM, un modèle est utilisé pour décrire la relation de mappage entre les types d'entités dans l'application et les tables de la base de données. Le modèle définit les propriétés et les relations de l'entité, ainsi que les configurations spécifiques à la base de données qui leur sont associées. Dans le framework ent, les modèles sont généralement utilisés pour décrire les types d'entités dans un graphe, tels que Utilisateur
ou Groupe
.
Les définitions de modèles incluent généralement des descriptions des champs (ou propriétés) de l'entité et des arêtes (ou relations), ainsi que certaines options spécifiques à la base de données. Ces descriptions nous aident à définir la structure, les propriétés et les relations de l'entité, et peuvent être utilisées pour générer la structure de table de base de données correspondante en fonction du modèle.
1.2. Aperçu des champs
Les champs sont la partie du modèle qui représente les propriétés de l'entité. Ils définissent les propriétés de l'entité, telles que le nom, l'âge, la date, etc. Dans le framework ent, les types de champs incluent divers types de données de base, tels que entier, chaîne de caractères, booléen, heure, etc., ainsi que certains types spécifiques à SQL, tels que UUID, []byte, JSON, etc.
Le tableau ci-dessous montre les types de champs pris en charge par le framework ent :
Type | Description |
---|---|
int | Type entier |
uint8 | Type entier non signé sur 8 bits |
float64 | Type de nombre à virgule |
bool | Type booléen |
string | Type chaîne de caractères |
time.Time | Type temps |
UUID | Type UUID |
[]byte | Type tableau d'octets (uniquement SQL) |
JSON | Type JSON (uniquement SQL) |
Enum | Type enum (uniquement SQL) |
Autre | Autres types (par exemple, plage Postgres) |
2. Détails des propriétés des champs
2.1. Types de données
Le type de données d'un attribut ou d'un champ dans un modèle d'entité détermine la forme des données pouvant être stockées. Il s'agit d'une partie cruciale de la définition du modèle dans le framework ent. Voici quelques types de données couramment utilisés dans le framework ent
.
import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// Schéma de l'utilisateur.
type Utilisateur struct {
ent.Schema
}
// Champs de l'utilisateur.
func (Utilisateur) Fields() []ent.Field {
return []ent.Field{
field.Int("age"), // Type entier
field.String("nom"), // Type chaîne de caractères
field.Bool("actif"), // Type booléen
field.Float("score"), // Type à virgule flottante
field.Time("créé_le"), // Type horodatage
}
}
-
int
: Représente des valeurs entières, pouvant êtreint8
,int16
,int32
,int64
, etc. -
string
: Représente des données de type chaîne de caractères. -
bool
: Représente des valeurs booléennes, généralement utilisé comme des indicateurs. -
float64
: Représente des nombres à virgule flottante, peut également utiliserfloat32
. -
time.Time
: Représente le temps, généralement utilisé pour les horodatages ou les données de date.
Ces types de champs seront mappés sur les types correspondants pris en charge par la base de données sous-jacente. De plus, ent
prend en charge des types plus complexes tels que UUID
, JSON
, des énumérations (Enum
), et prend en charge des types de base de données spéciaux comme []byte
(uniquement SQL) et Autre
(uniquement SQL).
2.2. Valeurs par défaut
Les champs peuvent être configurés avec des valeurs par défaut. Si la valeur correspondante n'est pas spécifiée lors de la création d'une entité, la valeur par défaut sera utilisée. La valeur par défaut peut être une valeur fixe ou une valeur générée dynamiquement à partir d'une fonction. Utilisez la méthode .Default
pour définir une valeur par défaut statique, ou utilisez .DefaultFunc
pour définir une valeur par défaut générée dynamiquement.
// Schéma de l'utilisateur.
func (Utilisateur) Fields() []ent.Field {
return []ent.Field{
field.Time("créé_le").
Default(time.Now), // Une valeur par défaut fixe de time.Now
field.String("rôle").
Default("utilisateur"), // Une valeur de chaîne constante
field.Float("score").
DefaultFunc(func() float64 {
return 10.0 // Une valeur par défaut générée par une fonction
}),
}
}
2.3. Optionnalité des champs et valeurs nulles
Par défaut, les champs sont requis. Pour déclarer un champ optionnel, utilisez la méthode .Optional()
. Les champs optionnels seront déclarés comme des champs nullables dans la base de données. L'option Nillable
permet aux champs d'être explicitement définis comme nil
, distinguant ainsi entre la valeur zéro d'un champ et un état non défini.
// Schéma de l'utilisateur.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("surnom").Optional(), // Le champ facultatif n'est pas requis
field.Int("âge").Optional().Nillable(), // Le champ nillable peut être défini comme nul
}
}
Lors de l'utilisation du modèle défini ci-dessus, le champ âge
peut accepter à la fois des valeurs nil
pour indiquer une non-définition, ainsi que des valeurs zéro non-nil
.
2.4. Unicité des champs
Les champs uniques garantissent qu'il n'y a pas de valeurs en double dans la table de la base de données. Utilisez la méthode Unique()
pour définir un champ unique. Lorsque l'intégrité des données est une exigence critique, tel que pour les adresses e-mail d'utilisateurs ou les noms d'utilisateur, il convient d'utiliser des champs uniques.
// Schéma de l'utilisateur.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("email").Unique(), // Champ unique pour éviter les adresses e-mail en double
}
}
Cela créera une contrainte d'unicité dans la base de données sous-jacente pour empêcher l'insertion de valeurs en double.
2.5. Indexation des champs
L'indexation des champs est utilisée pour améliorer les performances des requêtes de base de données, en particulier dans les grandes bases de données. Dans le framework ent
, la méthode .Indexes()
peut être utilisée pour créer des index.
import "entgo.io/ent/schema/index"
// Schéma de l'utilisateur.
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("email"), // Créer un index sur le champ 'email'
index.Fields("nom", "âge").Unique(), // Créer un index composite unique
}
}
Les index peuvent être utilisés pour les champs fréquemment interrogés, mais il est important de noter que trop d'index peuvent entraîner une diminution des performances des opérations d'écriture. Par conséquent, la décision de créer des index doit être équilibrée en fonction des circonstances réelles.
2.6. Balises personnalisées
Dans le framework ent
, vous pouvez utiliser la méthode StructTag
pour ajouter des balises personnalisées aux champs de la structure d'entité générée. Ces balises sont très utiles pour implémenter des opérations telles que l'encodage JSON et l'encodage XML. Dans l'exemple ci-dessous, nous ajouterons des balises JSON et XML personnalisées pour le champ nom
.
// Champs de l'utilisateur.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("nom").
// Ajouter des balises personnalisées à l'aide de la méthode StructTag
// Ici, définissez la balise JSON pour le champ nom sur 'nom_utilisateur' et ignorez-la lorsque le champ est vide (omitempty)
// De plus, définissez la balise XML pour l'encodage sur 'nom'
StructTag(`json:"nom_utilisateur,omitempty" xml:"nom"`),
}
}
Lors de l'encodage avec JSON ou XML, l'option omitempty
indique que si le champ nom
est vide, alors ce champ sera omis du résultat d'encodage. Cela est très utile pour réduire la taille du corps de la réponse lors de l'écriture d'API.
Cela démontre également comment définir plusieurs balises pour le même champ simultanément. Les balises JSON utilisent la clé json
, les balises XML utilisent la clé xml
, et elles sont séparées par des espaces. Ces balises seront utilisées par les fonctions de bibliothèque telles que encoding/json
et encoding/xml
lors de l'analyse de la structure pour l'encodage ou le décodage.
3. Validation des champs et contraintes
La validation des champs est un aspect important de la conception de base de données pour garantir la cohérence et la validité des données. Dans cette section, nous allons approfondir l'utilisation des validateurs intégrés, des validateurs personnalisés et diverses contraintes pour améliorer l'intégrité et la qualité des données dans le modèle d'entité.
3.1. Validateurs intégrés
Le framework fournit une série de validateurs intégrés pour effectuer des vérifications de validité des données courantes sur différents types de champs. L'utilisation de ces validateurs intégrés peut simplifier le processus de développement et définir rapidement des plages de données valides ou des formats pour les champs.
Voici quelques exemples de validateurs de champs intégrés :
- Validateurs pour les types numériques :
-
Positive()
: Valide si la valeur du champ est un nombre positif. -
Negative()
: Valide si la valeur du champ est un nombre négatif. -
NonNegative()
: Valide si la valeur du champ est un nombre non négatif. -
Min(i)
: Valide si la valeur du champ est supérieure à une valeur minimale donnéei
. -
Max(i)
: Valide si la valeur du champ est inférieure à une valeur maximale donnéei
.
-
- Validateurs pour le type
string
:-
MinLen(i)
: Valide la longueur minimale d'une chaîne. -
MaxLen(i)
: Valide la longueur maximale d'une chaîne. -
Match(regexp.Regexp)
: Valide si la chaîne correspond à l'expression régulière donnée. -
NotEmpty
: Valide si la chaîne n'est pas vide.
-
Jetons un œil à un exemple de code pratique. Dans cet exemple, un modèle Utilisateur
est créé, qui comprend un champ de type entier non négatif âge
et un champ email
avec un format fixe :
func (Utilisateur) Champs() []ent.Field {
return []ent.Field{
field.Int("âge").
Positive(),
field.String("email").
Match(regexp.MustCompile(`^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$`)),
}
}
3.2. Validateurs personnalisés
Bien que les validateurs intégrés puissent gérer de nombreuses exigences de validation courantes, parfois vous pouvez avoir besoin d'une logique de validation plus complexe. Dans de tels cas, vous pouvez écrire des validateurs personnalisés pour répondre à des règles métier spécifiques.
Un validateur personnalisé est une fonction qui reçoit une valeur de champ et renvoie une error
. Si l'error
renvoyée n'est pas vide, cela indique une validation en échec. Le format général d'un validateur personnalisé est le suivant :
func (Utilisateur) Champs() []ent.Field {
return []ent.Field{
field.String("téléphone").
Validate(func(s string) error {
// Vérifie si le numéro de téléphone répond au format attendu
correspondance, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, s)
if !correspondance {
return errors.New("Format de numéro de téléphone incorrect")
}
return nil
}),
}
}
Comme le montre l'exemple ci-dessus, nous avons créé un validateur personnalisé pour valider le format d'un numéro de téléphone.
3.3. Contraintes
Les contraintes sont des règles qui imposent des règles spécifiques sur un objet de base de données. Elles peuvent être utilisées pour garantir la correction et la cohérence des données, telles que la prévention de la saisie de données invalides ou la définition des relations de données.
Les contraintes de base de données courantes comprennent :
- Contrainte de clé primaire : Garantit que chaque enregistrement dans la table est unique.
- Contrainte d'unicité : Garantit que la valeur d'une colonne ou d'une combinaison de colonnes est unique dans la table.
- Contrainte de clé étrangère : Définit les relations entre les tables et garantit l'intégrité référentielle.
- Contrainte de vérification : Garantit qu'une valeur de champ satisfait une condition spécifique.
Dans le modèle d'entité, vous pouvez définir des contraintes pour maintenir l'intégrité des données comme suit :
func (Utilisateur) Champs() []ent.Field {
return []ent.Field{
field.String("nom_utilisateur").
Unique(), // Contrainte d'unicité pour garantir que le nom d'utilisateur est unique dans la table.
field.String("email").
Unique(), // Contrainte d'unicité pour garantir que l'e-mail est unique dans la table.
}
}
func (Utilisateur) Relations() []ent.Relation {
return []ent.Relation{
edge.To("amis", Utilisateur.Type).
Unique(), // Contrainte de clé étrangère, créant une relation de bord unique avec un autre utilisateur.
}
}
En résumé, la validation des champs et les contraintes sont cruciales pour garantir une bonne qualité des données et éviter les erreurs de données inattendues. L'utilisation des outils fournis par le framework ent
peut rendre ce processus plus simple et plus fiable.