"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Gorm: adelanto de los tipos de datos personalizados

Gorm: adelanto de los tipos de datos personalizados

Publicado el 2024-11-08
Navegar:120

¡¿Bienvenidos de nuevo, amigos ?! Hoy, analizamos un caso de uso específico al que podríamos enfrentarnos al mover datos de un lado a otro desde/hacia la base de datos. Primero, permítanme establecer los límites del desafío de hoy. Para ceñirnos a un ejemplo de la vida real, tomemos prestados algunos conceptos del ejército de los EE. UU. Nuestro trato es escribir un pequeño software para guardar y leer a los oficiales con las calificaciones que han obtenido en sus carreras.

Tipos de datos personalizados de Gorm

Nuestro software necesita manejar a los oficiales del ejército con sus respectivos grados. A primera vista, puede parecer fácil y probablemente no necesitemos ningún tipo de datos personalizados aquí. Sin embargo, para mostrar esta característica, usemos una forma no convencional de representar los datos. Gracias a esto, se nos pide que definamos un mapeo personalizado entre las estructuras Go y las relaciones DB. Además, debemos definir una lógica específica para analizar los datos. Ampliemos esto analizando los objetivos del programa.

Caso de uso para manejar

Para simplificar las cosas, usemos un dibujo para representar las relaciones entre el código y los objetos SQL:

Gorm: Sneak Peek of Custom Data Types

Centrémonos en cada contenedor uno por uno.

¿Las estructuras Go?

Aquí, definimos dos estructuras. La estructura Grado contiene una lista no exhaustiva de grados militares. Esta estructura no será una tabla en la base de datos. Por el contrario, la estructura Oficial contiene el ID, el nombre y un puntero a la estructura Grado, que indica qué calificaciones ha obtenido el oficial hasta el momento.

Siempre que escribimos un oficial en la base de datos, la columna grados_alcanzados debe contener una serie de cadenas completadas con las calificaciones obtenidas (las que tienen verdadero en la estructura Grado).

¿Las relaciones DB?

En cuanto a los objetos SQL, solo tenemos la tabla de oficiales. Las columnas de identificación y nombre se explican por sí mismas. Luego, tenemos la columna grados_alcanzados que contiene las calificaciones del oficial en una colección de cadenas.

Cada vez que decodificamos a un oficial de la base de datos, analizamos la columna grados_alcanzados y creamos una "instancia" coincidente de la estructura Grado.

Quizás hayas notado que el comportamiento no es el estándar. Debemos hacer algunos arreglos para cumplirlo de la forma deseada.

Aquí, el diseño de los modelos es demasiado complicado a propósito. Utilice soluciones más sencillas siempre que sea posible.

Tipos de datos personalizados

Gorm nos proporciona tipos de datos personalizados. Nos dan una gran flexibilidad a la hora de definir la recuperación y el guardado hacia/desde la base de datos. Debemos implementar dos interfaces: Scanner y Valuer?. El primero especifica un comportamiento personalizado que se aplicará al recuperar datos de la base de datos. Este último indica cómo escribir valores en la base de datos. Ambos nos ayudan a lograr la lógica de mapeo no convencional que necesitamos.

Las firmas de las funciones que debemos implementar son Scan(value interface{}) error y Value() (driver.Value, error). Ahora, veamos el código.

El código

El código de este ejemplo se encuentra en dos archivos: el dominio/models.go y main.go. Comencemos con el primero, que trata de los modelos (traducidos como estructuras en Go).

El archivo dominio/models.go

Primero, déjame presentarte el código de este archivo:

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
}

Ahora, ¿resaltemos las partes relevantes del mismo?:

  1. La estructura Grade solo enumera las calificaciones que pronosticamos en nuestro software
  2. La estructura Oficial define las características de la entidad. Esta entidad es una relación en la base de datos. Aplicamos dos notaciones de Gorm:
    1. gorm:"primaryKey" en el campo ID para definirlo como la clave principal de nuestra relación
    2. gorm:"type:varchar[]" para asignar el campo GradesAchieved como una matriz de varchar en la base de datos. De lo contrario, se traduce como una tabla de base de datos separada o columnas adicionales en la tabla de oficiales
  3. La estructura Calificación implementa la función Escanear. Aquí obtenemos el valor bruto, lo ajustamos, configuramos algunos campos en la variable g y devolvemos
  4. La estructura Grade también implementa la función Valor como un tipo de receptor de valor (no necesitamos cambiar el receptor esta vez, no usamos la referencia *). Devolvemos el valor para escribir en la columna calificaciones_alcanzadas de la tabla de oficiales

Gracias a estos dos métodos, podemos controlar cómo enviar y recuperar el tipo Grade durante las interacciones con la BD. Ahora, veamos el archivo main.go.

¿El archivo main.go?

Aquí, preparamos la conexión de base de datos, migramos los objetos a relaciones (ORM significa Object Relation Mapping), e insertamos y recuperamos registros para probar la lógica. A continuación se muestra el código:

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))
}

Ahora, veamos las secciones relevantes de este archivo. Primero, definimos la función seedDB para agregar datos ficticios en la base de datos. Los datos se encuentran en el archivo data.sql con el siguiente contenido:

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 función main() comienza configurando una conexión de base de datos. Para esta demostración, utilizamos PostgreSQL. Luego, nos aseguramos de que la tabla de oficiales exista en la base de datos y esté actualizada con la versión más reciente de la estructura models.Officer. Dado que este programa es una muestra, hicimos dos cosas adicionales:

  • Eliminación de la tabla al final de la función main() (cuando finalice el programa, también nos gustaría eliminar la tabla)
  • Semilla de algunos datos ficticios

Por último, para asegurarnos de que todo funcione como se espera, hacemos un par de cosas:

  1. Obteniendo todos los registros en la BD
  2. Agregar (y recuperar) un nuevo oficial

Eso es todo por este archivo. Ahora, ¿probemos nuestro trabajo?.

El momento de la verdad

Antes de ejecutar el código, asegúrese de que se esté ejecutando una instancia de PostgreSQL en su máquina. Con Docker ?, puedes ejecutar este comando:

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

Ahora podemos ejecutar nuestra aplicación de forma segura emitiendo el comando: go run. ?

La salida es:

[
        {
                "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á! Todo funciona como se esperaba. Podemos volver a ejecutar el código varias veces y obtener siempre el mismo resultado.

eso es un envoltorio

Espero que hayas disfrutado de esta publicación de blog sobre Gorm y los Tipos de datos personalizados. Siempre recomiendo que te ciñas al enfoque más sencillo. Opte por esto sólo si eventualmente lo necesita. Este enfoque agrega flexibilidad a cambio de hacer que el código sea más complejo y menos robusto (un pequeño cambio en las definiciones de las estructuras podría generar errores y requerir trabajo adicional).

Ten esto en cuenta. Si se apega a las convenciones, puede ser menos detallado en todo su código base.

Esa es una gran cita para finalizar esta publicación de blog.
Si se da cuenta de que se necesitan tipos de datos personalizados, esta publicación de blog debería ser un buen punto de partida para presentarle una solución funcional.

Por favor, déjame saber tus sentimientos y pensamientos. ¡Cualquier comentario siempre es apreciado! Si está interesado en un tema específico, comuníquese con nosotros y lo seleccionaré. ¡Hasta la próxima, mantente a salvo y hasta pronto!

Declaración de liberación Este artículo se reproduce en: https://dev.to/ossan/gorm-sneak-peek-of-custom-data-types-97n?1 Si hay alguna infracción, comuníquese con [email protected] para eliminarla.
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3