"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Gorm : aperçu des types de données personnalisés

Gorm : aperçu des types de données personnalisés

Publié le 2024-11-08
Parcourir:201

Bienvenue, les amis ?! Aujourd'hui, nous discutons d'un cas d'utilisation spécifique auquel nous pourrions être confrontés lors du déplacement de données d'avant en arrière depuis/vers la base de données. Tout d’abord, permettez-moi de fixer les limites du défi d’aujourd’hui. Pour s'en tenir à un exemple concret, empruntons quelques concepts à l'armée américaine ?. Notre contrat est d'écrire un petit logiciel pour enregistrer et lire les officiers avec les notes qu'ils ont obtenues au cours de leur carrière.

Types de données personnalisés de Gorm

Notre logiciel doit gérer les officiers de l'armée avec leurs grades respectifs. À première vue, cela peut sembler simple et nous n'avons probablement pas besoin d'un type de données personnalisé ici. Cependant, pour mettre en valeur cette fonctionnalité, utilisons une manière non conventionnelle de représenter les données. Grâce à cela, on nous demande de définir un mappage personnalisé entre les structures Go et les relations DB. De plus, nous devons définir une logique spécifique pour analyser les données. Développons cela en examinant les objectifs du programme ?.

Cas d'utilisation à gérer

Pour faciliter les choses, utilisons un dessin pour représenter les relations entre le code et les objets SQL :

Gorm: Sneak Peek of Custom Data Types

Concentrons-nous sur chaque conteneur un par un.

Les Go Structs ?

Ici, nous avons défini deux structures. La structure Grade contient une liste non exhaustive de grades militaires ?️. Cette structure ne sera pas une table dans la base de données. À l'inverse, la structure Officier contient l'ID, le nom et un pointeur vers la structure Grade, indiquant les grades obtenus par l'officier jusqu'à présent.

Chaque fois que nous écrivons un officier dans la base de données, la colonne grades_achieved doit contenir un tableau de chaînes remplies avec les notes obtenues (celles avec true dans la structure Grade).

Les relations DB ?

Concernant les objets SQL, nous n'avons que la table des officiers. Les colonnes id et name sont explicites. Ensuite, nous avons la colonne grades_achieved qui contient les notes de l'officier dans une collection de chaînes.

Chaque fois que nous décodons un officier de la base de données, nous analysons la colonne grades_achieved et créons une « instance » correspondante de la structure Grade.

Vous avez peut-être remarqué que le comportement n’est pas standard. Nous devons prendre certaines dispositions pour le réaliser de la manière souhaitée.

Ici, la disposition des modèles est volontairement trop compliquée. Veuillez vous en tenir à des solutions plus simples autant que possible.

Types de données personnalisés

Gorm nous fournit des types de données personnalisés. Ils nous donnent une grande flexibilité dans la définition de la récupération et de la sauvegarde vers/depuis la base de données. Il faut implémenter deux interfaces : Scanner et Valuer ?. Le premier spécifie un comportement personnalisé à appliquer lors de la récupération des données de la base de données. Ce dernier indique comment écrire les valeurs dans la base de données. Les deux nous aident à réaliser la logique de cartographie non conventionnelle dont nous avons besoin.

Les signatures des fonctions que nous devons implémenter sont Scan(value interface{}) error et Value() (driver.Value, error). Maintenant, regardons le code.

Le code

Le code de cet exemple se trouve dans deux fichiers : le domain/models.go et le main.go. Commençons par le premier, celui des modèles (traduits par structs dans Go).

Le fichier domaine/models.go

Tout d'abord, permettez-moi de vous présenter le code de ce fichier :

package models

import (
 "database/sql/driver"
 "slices"
 "strings"
)

type Grade struct {
 Lieutenant bool
 Captain    bool
 Colonel    bool
 General    bool
}

type Officer struct {
 ID             uint64 `gorm:"primaryKey"`
 Name           string
 GradesAchieved *Grade `gorm:"type:varchar[]"`
}

func (g *Grade) Scan(value interface{}) error {
 // we should have utilized the "comma, ok" idiom
 valueRaw := value.(string)
 valueRaw = strings.Replace(strings.Replace(valueRaw, "{", "", -1), "}", "", -1)
 grades := strings.Split(valueRaw, ",")
 if slices.Contains(grades, "lieutenant") {
 g.Lieutenant = true
 }
 if slices.Contains(grades, "captain") {
 g.Captain = true
 }
 if slices.Contains(grades, "colonel") {
 g.Colonel = true
 }
 if slices.Contains(grades, "general") {
 g.General = true
 }
 return nil
}

func (g Grade) Value() (driver.Value, error) {
 grades := make([]string, 0, 4)
 if g.Lieutenant {
 grades = append(grades, "lieutenant")
 }
 if g.Captain {
 grades = append(grades, "captain")
 }
 if g.Colonel {
 grades = append(grades, "colonel")
 }
 if g.General {
 grades = append(grades, "general")
 }
 return grades, nil
}

Maintenant, soulignons les parties pertinentes ? :

  1. La structure Grade répertorie uniquement les notes que nous avons prévues dans notre logiciel
  2. La structure Officer définit les caractéristiques de l'entité. Cette entité est une relation dans la base de données. Nous avons appliqué deux notations Gorm :
    1. gorm:"primaryKey" sur le champ ID pour le définir comme clé primaire de notre relation
    2. gorm:"type:varchar[]" pour mapper le champ GradesAchieved en tant que tableau de varchar dans la base de données. Sinon, cela se traduit par une table DB distincte ou des colonnes supplémentaires dans la table des officiers
  3. La structure Grade implémente la fonction Scan. Ici, on récupère la valeur brute, on l'ajuste, on définit quelques champs sur la variable g, et on retourne
  4. La structure Grade implémente également la fonction Value en tant que type de récepteur de valeur (nous n'avons pas besoin de changer de récepteur cette fois, nous n'utilisons pas la référence *). On renvoie la valeur à écrire dans la colonne grades_achieved de la table des officiers

Grâce à ces deux méthodes, nous pouvons contrôler la manière d'envoyer et de récupérer le type Grade lors des interactions avec la base de données. Maintenant, regardons le fichier main.go.

Le fichier main.go ?

Ici, nous préparons la connexion à la base de données, migrons les objets vers les relations (ORM signifie Object Relation Mapping), et insérons et récupérons enregistrements pour tester la logique. Ci-dessous le code :

package main

import (
 "encoding/json"
 "fmt"
 "os"

 "gormcustomdatatype/models"

 "gorm.io/driver/postgres"
 "gorm.io/gorm"
)

func seedDB(db *gorm.DB, file string) error {
 data, err := os.ReadFile(file)
 if err != nil {
  return err
 }
 if err := db.Exec(string(data)).Error; err != nil {
  return err
 }
 return nil
}

// docker run -d -p 54322:5432 -e POSTGRES_PASSWORD=postgres postgres
func main() {
 dsn := "host=localhost port=54322 user=postgres password=postgres dbname=postgres sslmode=disable"
 db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
 if err != nil {
 fmt.Fprintf(os.Stderr, "could not connect to DB: %v", err)
  return
 }
 db.AutoMigrate(&models.Officer{})
 defer func() {
 db.Migrator().DropTable(&models.Officer{})
 }()
 if err := seedDB(db, "data.sql"); err != nil {
 fmt.Fprintf(os.Stderr, "failed to seed DB: %v", err)
  return
 }
 // print all the officers
 var officers []models.Officer
 if err := db.Find(&officers).Error; err != nil {
 fmt.Fprintf(os.Stderr, "could not get the officers from the DB: %v", err)
  return
 }
 data, _ := json.MarshalIndent(officers, "", "\t")
 fmt.Fprintln(os.Stdout, string(data))

 // add a new officer
 db.Create(&models.Officer{
 Name: "Monkey D. Garp",
 GradesAchieved: &models.Grade{
 Lieutenant: true,
 Captain:    true,
 Colonel:    true,
 General:    true,
  },
 })
 var garpTheHero models.Officer
 if err := db.First(&garpTheHero, 4).Error; err != nil {
 fmt.Fprintf(os.Stderr, "failed to get officer from the DB: %v", err)
  return
 }
 data, _ = json.MarshalIndent(&garpTheHero, "", "\t")
 fmt.Fprintln(os.Stdout, string(data))
}

Voyons maintenant les sections pertinentes de ce fichier. Tout d’abord, nous définissons la fonction seedDB pour ajouter des données factices dans la base de données. Les données se trouvent dans le fichier data.sql avec le contenu suivant :

INSERT INTO public.officers
(id, "name", grades_achieved)
VALUES(nextval('officers_id_seq'::regclass), 'john doe', '{captain,lieutenant}'),
(nextval('officers_id_seq'::regclass), 'gerard butler', '{general}'),
(nextval('officers_id_seq'::regclass), 'chuck norris', '{lieutenant,captain,colonel}');

La fonction main() commence par établir une connexion DB. Pour cette démo, nous avons utilisé PostgreSQL. Ensuite, nous nous assurons que la table des officiers existe dans la base de données et qu'elle est à jour avec la dernière version de la structure models.Officer. Puisque ce programme est un exemple, nous avons effectué deux choses supplémentaires :

  • Suppression de la table à la fin de la fonction main() (à la fin du programme, nous souhaitons également supprimer la table)
  • Amorçage de certaines données factices

Enfin, pour nous assurer que tout fonctionne comme prévu, nous procédons comme suit :

  1. Récupération de tous les enregistrements de la base de données
  2. Ajouter (et récupérer) un nouvel officier

C'est tout pour ce fichier. Maintenant, testons notre travail ?.

Le moment de vérité

Avant d'exécuter le code, veuillez vous assurer qu'une instance PostgreSQL est en cours d'exécution sur votre machine. Avec Docker ?, vous pouvez exécuter cette commande :

docker run -d -p 54322:5432 -e POSTGRES_PASSWORD=postgres postgres

Maintenant, nous pouvons exécuter notre application en toute sécurité en émettant la commande : go run . ?

Le résultat est :

[
        {
                "ID": 1,
                "Name": "john doe",
                "GradesAchieved": {
                        "Lieutenant": true,
                        "Captain": true,
                        "Colonel": false,
                        "General": false
                }
        },
        {
                "ID": 2,
                "Name": "gerard butler",
                "GradesAchieved": {
                        "Lieutenant": false,
                        "Captain": false,
                        "Colonel": false,
                        "General": true
                }
        },
        {
                "ID": 3,
                "Name": "chuck norris",
                "GradesAchieved": {
                        "Lieutenant": true,
                        "Captain": true,
                        "Colonel": true,
                        "General": false
                }
        }
]
{
        "ID": 4,
        "Name": "Monkey D. Garp",
        "GradesAchieved": {
                "Lieutenant": true,
                "Captain": true,
                "Colonel": true,
                "General": true
        }
}

Voilà ! Tout fonctionne comme prévu. Nous pouvons réexécuter le code plusieurs fois et avoir toujours le même résultat.

C'est un emballage

J'espère que vous avez apprécié cet article de blog concernant Gorm et les Types de données personnalisés. Je vous recommande toujours de vous en tenir à l’approche la plus simple. Optez pour cela uniquement si vous en avez éventuellement besoin. Cette approche ajoute de la flexibilité en échange de rendre le code plus complexe et moins robuste (un petit changement dans les définitions des structures pourrait entraîner des erreurs et un travail supplémentaire nécessaire).

Gardez cela à l’esprit. Si vous respectez les conventions, vous pouvez être moins verbeux dans votre base de code.

C'est une excellente citation pour terminer cet article de blog.
Si vous réalisez que des types de données personnalisés sont nécessaires, cet article de blog devrait être un bon point de départ pour vous présenter une solution efficace.

S'il vous plaît, faites-moi part de vos sentiments et de vos pensées. Tout commentaire est toujours apprécié ! Si un sujet spécifique vous intéresse, contactez-nous et je le présélectionnerai. En attendant la prochaine fois, restez prudent et à bientôt !

Déclaration de sortie Cet article est reproduit sur : https://dev.to/ossan/gorm-sneak-peek-of-custom-data-types-97n?1 En cas de violation, veuillez contacter [email protected] pour le supprimer.
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3