Skip to content

Instantly share code, notes, and snippets.

@nejdetkadir
Created November 14, 2024 21:08
Show Gist options
  • Save nejdetkadir/ff55f4e4c81592bd06d51a82062bae9e to your computer and use it in GitHub Desktop.
Save nejdetkadir/ff55f4e4c81592bd06d51a82062bae9e to your computer and use it in GitHub Desktop.
Golang + Postgresql Pessimistic and Optimistic locking
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
)
type (
Product struct {
ID int
Stock int
Version int
}
)
func main() {
app := fiber.New()
db, err := gorm.Open(postgres.Open("<your connection url>"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
log.Fatalf("Error connecting to database: %v", err)
}
err = db.AutoMigrate(&Product{})
if err != nil {
log.Fatalf("Error migrating schema: %v", err)
}
var productCount int64
db.Model(&Product{}).Count(&productCount)
if productCount == 0 {
result := db.Create(&Product{Stock: 100, Version: 0})
if result.Error != nil {
log.Fatalf("Error creating product: %v", result.Error)
}
log.Info("Product created")
} else {
var product Product
result := db.First(&product)
if result.Error != nil {
log.Fatalf("Error reading product: %v", result.Error)
}
product.Stock = 100
product.Version = 0
result = db.Save(&product)
if result.Error != nil {
log.Fatalf("Error updating product: %v", result.Error)
}
log.Info("Product updated")
}
pessimisticGroup := app.Group("/pessimistic")
optimisticGroup := app.Group("/optimistic")
normalGroup := app.Group("/normal")
pessimisticGroup.Post("/purchase", func(c *fiber.Ctx) error {
var product Product
tx := db.Begin()
if err = tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&product).Error; err != nil {
tx.Rollback()
return c.Status(fiber.StatusInternalServerError).SendString("Error reading product")
}
if product.Stock > 0 {
product.Stock--
if err = tx.Save(&product).Error; err != nil {
tx.Rollback()
return c.Status(fiber.StatusInternalServerError).SendString("Error updating stock")
}
tx.Commit()
return c.JSON(product)
}
tx.Rollback()
return c.SendStatus(fiber.StatusConflict)
})
optimisticGroup.Post("/purchase", func(c *fiber.Ctx) error {
var product Product
tx := db.Begin()
if err = tx.First(&product).Error; err != nil {
tx.Rollback()
return c.Status(fiber.StatusInternalServerError).SendString("Error reading product")
}
if product.Stock > 0 {
if err = tx.Model(&Product{}).
Where("id = ? AND version = ?", product.ID, product.Version).
Updates(map[string]interface{}{
"stock": product.Stock - 1,
"version": product.Version + 1,
}).Error; err != nil {
tx.Rollback()
return c.Status(fiber.StatusConflict).SendString("Optimistic lock conflict")
}
tx.Commit()
return c.JSON(product)
}
tx.Rollback()
return c.SendStatus(fiber.StatusConflict)
})
normalGroup.Post("/purchase", func(c *fiber.Ctx) error {
var product Product
tx := db.Begin()
if err = tx.First(&product).Error; err != nil {
tx.Rollback()
return c.Status(fiber.StatusInternalServerError).SendString("Error reading product")
}
if product.Stock > 0 {
product.Stock--
if err = tx.Save(&product).Error; err != nil {
tx.Rollback()
return c.Status(fiber.StatusInternalServerError).SendString("Error updating stock")
}
tx.Commit()
return c.JSON(product)
}
tx.Rollback()
return c.SendStatus(fiber.StatusConflict)
})
log.Fatal(app.Listen(":3000"))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment