Rafael

Engenheiro de Software

Olá, Eu sou Rafael. Estou construindo coisas para a web e mobile.

Um desenvolvedor dedicado a criar experiências digitais de alto desempenho, acessíveis e limpas. Eu me especializo em transformar problemas complexos em soluções de engenharia elegantes.

SQLC e Transação

O que é uma transação? – Uma transação é um conjunto de operações SQL que são tratadas como uma única unidade de trabalho. Ou todas as operações são aplicadas com sucesso, ou nenhuma delas é aplicada. O objetivo é garantir a consistência dos dados.

No nosso post anterior vimos sobre SQLC e Migração, agora falamos ver como fazer uma transação em SQLC.

Crie na pasta cmd a pasta execSQLCTX e adicone um arquivo main.go, feito isso só analisarmos o código do main.go:

GO
package main

import (
	"context"
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/google/uuid"
	"github.com/rafaelsouzaribeiro/sqlc/internal/db"
)

type Library struct {
	dbConn *sql.DB
	*db.Queries
}

func NewLibrary(dbConn *sql.DB) *Library {
	return &Library{
		dbConn:  dbConn,
		Queries: db.New(dbConn),
	}
}

type Author struct {
	ID   string
	Name string
	Bio  sql.NullString
}

type Books struct {
	ID          string
	Name        string
	Description sql.NullString
	AuthorsID   string
	Price       float64
}

func (c *Library) callTx(ctx context.Context, fn func(*db.Queries) error) error {
	tx, err := c.dbConn.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	qtx := c.WithTx(tx)
	err = fn(qtx)
	if err != nil {
		if errRb := tx.Rollback(); errRb != nil {
			return fmt.Errorf("error on rollback: %v, original error: %w", errRb, err)
		}
		return err
	}
	return tx.Commit()
}

func (c *Library) CreateAuthorAndBook(ctx context.Context, argsAuthor Author, argsBook Books) error {
	if argsAuthor.ID == "" || argsAuthor.Name == "" {
		return fmt.Errorf("author ID and Name are required")
	}
	if argsBook.Name == "" || argsBook.AuthorsID == "" || argsBook.Price == 0 {
		return fmt.Errorf("book ID, Name, AuthorsID and Price are required")
	}

	err := c.callTx(ctx, func(q *db.Queries) error {
		var err error
		err = q.CreateAuthor(ctx, db.CreateAuthorParams{
			ID:   argsAuthor.ID,
			Name: argsAuthor.Name,
			Bio:  argsAuthor.Bio,
		})
		if err != nil {
			return err
		}
		err = q.CreateBook(ctx, db.CreateBookParams{
			ID:          argsBook.ID,
			Name:        argsBook.Name,
			Description: argsBook.Description,
			AuthorsID:   argsBook.AuthorsID,
			Price:       argsBook.Price,
		})
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return err
	}
	return nil
}

func main() {
	ctx := context.Background()
	dbConn, err := sql.Open("mysql", "root:root@tcp(localhost:3306)/library")
	if err != nil {
		panic(err)
	}
	defer dbConn.Close()

	queries := db.New(dbConn)

	authorArgs := Author{
		ID:   uuid.New().String(),
		Name: "John Doe",
		Bio:  sql.NullString{String: "Author Bio", Valid: true},
	}

	bookArgs := Books{
		ID:          uuid.New().String(),
		Name:        "Go",
		Description: sql.NullString{String: "Go Course", Valid: true},
		AuthorsID:   authorArgs.ID,
		Price:       24.99,
	}

	library := NewLibrary(dbConn)
	err = library.CreateAuthorAndBook(ctx, authorArgs, bookArgs)
	if err != nil {
		panic(err)
	}

	books, err := queries.ListBooks(ctx)
	if err != nil {
		panic(err)
	}
	for _, book := range books {
		println("Book:", book.Name, "Author:", book.AuthorName, "Price:", book.Price)
	}
}

Veja que nossa função callTx fica com a obrigação de verificar se os nossos comandos estão todos corretos antes de inserir qualquer comando SQL. Agora é só executar o arquivo main.go e está feito nossa transação.

0 comentários

Deixe seu comentário