"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Gorm: uma prévia dos tipos de dados personalizados

Gorm: uma prévia dos tipos de dados personalizados

Publicado em 2024-11-08
Navegar:859

Bem-vindos de volta, pessoal?! Hoje, discutimos um caso de uso específico que podemos enfrentar ao mover dados de/para o banco de dados. Primeiro, deixe-me definir os limites do desafio de hoje. Para seguir um exemplo da vida real, vamos pegar emprestados alguns conceitos do Exército dos EUA? Nosso negócio é escrever um pequeno software para salvar e ler os oficiais com as notas que alcançaram em suas carreiras.

Tipos de dados personalizados do Gorm

Nosso software precisa lidar com os oficiais do exército com suas respectivas graduações. À primeira vista, pode parecer fácil e provavelmente não precisamos de nenhum tipo de dados personalizado aqui. Porém, para mostrar esse recurso, vamos usar uma forma não convencional de representar os dados. Graças a isso, somos solicitados a definir um mapeamento personalizado entre estruturas Go e relações de banco de dados. Além disso, devemos definir uma lógica específica para analisar os dados. Vamos expandir isso observando as metas do programa?.

Use o caso para lidar

Para facilitar, vamos usar um desenho para representar as relações entre o código e os objetos SQL:

Gorm: Sneak Peek of Custom Data Types

Vamos nos concentrar em cada contêiner, um por um.

As estruturas Go?

Aqui, definimos duas estruturas. A estrutura Grade contém uma lista não exaustiva de graus militares ?️. Esta estrutura não será uma tabela no banco de dados. Por outro lado, a estrutura Officer contém o ID, o nome e um ponteiro para a estrutura Grade, indicando quais notas foram alcançadas pelo oficial até o momento.

Sempre que escrevemos um oficial no banco de dados, a coluna grades_achieved deve conter um array de strings preenchidas com as notas alcançadas (aquelas com true na estrutura Grade).

As relações do banco de dados?

Em relação aos objetos SQL, temos apenas a tabela de oficiais. As colunas id e name são autoexplicativas. Então, temos a coluna grades_achieved que contém as notas do oficial em uma coleção de strings.

Sempre que decodificamos um oficial do banco de dados, analisamos a coluna grades_achieved e criamos uma "instância" correspondente da estrutura Grade.

Você deve ter notado que o comportamento não é o padrão. Devemos tomar algumas providências para cumpri-lo da maneira desejada.

Aqui, o layout dos modelos é propositalmente complicado demais. Por favor, opte por soluções mais diretas sempre que possível.

Tipos de dados personalizados

Gorm nos fornece tipos de dados personalizados. Eles nos dão grande flexibilidade na definição da recuperação e salvamento de/para o banco de dados. Devemos implementar duas interfaces: Scanner e Valuer?. O primeiro especifica um comportamento personalizado a ser aplicado ao buscar dados do banco de dados. Este último indica como escrever valores no banco de dados. Ambos nos ajudam a alcançar a lógica de mapeamento não convencional de que precisamos.

As assinaturas das funções que devemos implementar são Scan(value interface{}) error e Value() (driver.Value, error). Agora, vamos dar uma olhada no código.

O Código

O código deste exemplo reside em dois arquivos: o domínio/models.go e o main.go. Vamos começar com o primeiro, tratando dos modelos (traduzidos como structs em Go).

O arquivo domínio/models.go

Primeiro, deixe-me apresentar o código deste arquivo:

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
}

Agora, vamos destacar as partes relevantes dele?:

  1. A estrutura Grade lista apenas as notas que previmos em nosso software
  2. A estrutura Officer define as características da entidade. Esta entidade é uma relação no banco de dados. Aplicamos duas notações Gorm:
    1. gorm:"primaryKey" no campo ID para defini-lo como a chave primária da nossa relação
    2. gorm:"type:varchar[]" para mapear o campo GradesAchieved como uma matriz de varchar no banco de dados. Caso contrário, isso se traduz como uma tabela de banco de dados separada ou colunas adicionais na tabela de oficiais
  3. A estrutura Grade implementa a função Scan. Aqui, obtemos o valor bruto, ajustamos, definimos alguns campos na variável g e retornamos
  4. A estrutura Grade também implementa a função Value como um tipo de receptor de valor (não precisamos alterar o receptor desta vez, não usamos a referência *). Retornamos o valor a ser escrito na coluna notas_atingidas da tabela de oficiais

Graças a esses dois métodos, podemos controlar como enviar e recuperar o tipo Grade durante as interações do banco de dados. Agora, vamos dar uma olhada no arquivo main.go.

O arquivo main.go?

Aqui, preparamos a conexão do banco de dados, migramos os objetos para relações (ORM significa Object Relation Mapping) e inserimos e buscamos registros para testar a lógica. Abaixo está o 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))
}

Agora, vamos ver as seções relevantes deste arquivo. Primeiro, definimos a função seedDB para adicionar dados fictícios ao banco de dados. Os dados residem no arquivo data.sql com o seguinte conteúdo:

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

A função main() começa configurando uma conexão de banco de dados. Para esta demonstração, usamos PostgreSQL. Em seguida, garantimos que a tabela oficiais exista no banco de dados e esteja atualizada com a versão mais recente da estrutura models.Officer. Como este programa é um exemplo, fizemos duas coisas adicionais:

  • Remoção da tabela no final da função main() (quando o programa terminar, gostaríamos de remover a tabela também)
  • Semeando alguns dados fictícios

Por último, para garantir que tudo funcione conforme o esperado, fazemos algumas coisas:

  1. Buscando todos os registros no banco de dados
  2. Adicionando (e recuperando) um novo oficial

Isso é tudo para este arquivo. Agora, vamos testar nosso trabalho?.

O momento da verdade

Antes de executar o código, certifique-se de que uma instância do PostgreSQL esteja sendo executada em sua máquina. Com Docker?, você pode executar este comando:

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

Agora, podemos executar nosso aplicativo com segurança emitindo o comando: go run . ?

A saída é:

[
        {
                "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á! Tudo funciona conforme o esperado. Podemos executar novamente o código várias vezes e sempre obter a mesma saída.

Isso é um embrulho

Espero que você tenha gostado desta postagem do blog sobre Gorm e os Tipos de dados personalizados. Eu sempre recomendo que você siga a abordagem mais direta. Opte por isso apenas se eventualmente precisar. Essa abordagem adiciona flexibilidade em troca de tornar o código mais complexo e menos robusto (uma pequena mudança nas definições das estruturas pode levar a erros e à necessidade de trabalho extra).

Tenha isso em mente. Se você seguir as convenções, poderá ser menos detalhado em toda a sua base de código.

Essa é uma ótima citação para encerrar esta postagem do blog.
Se você perceber que são necessários tipos de dados personalizados, esta postagem do blog deve ser um bom ponto de partida para apresentar uma solução funcional.

Por favor, deixe-me saber seus sentimentos e pensamentos. Qualquer feedback é sempre apreciado! Se você estiver interessado em um tópico específico, entre em contato e eu o selecionarei. Até a próxima, fique seguro e até breve!

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/ossan/gorm-sneak-peek-of-custom-data-types-97n?1 Se houver alguma violação, entre em contato com [email protected] para excluí-la
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3