Sobes.tech
Senior

How to atomically implement availability check and order creation when moving logic to a service?

sobes.tech AI

Answer from AI

For atomic checks of availability and order creation in a Go service, database transactions are usually used. Within a single transaction, availability (for example, stock presence) is checked first, then the order is created. If something goes wrong, the transaction is rolled back, ensuring data consistency.

An example using a database and the database/sql package:

func CreateOrderIfAvailable(db *sql.DB, productID int, quantity int, userID int) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()

    var available int
    err = tx.QueryRow("SELECT stock FROM products WHERE id = ? FOR UPDATE", productID).Scan(&available)
    if err != nil {
        return err
    }

    if available < quantity {
        return fmt.Errorf("not enough stock")
    }

    _, err = tx.Exec("UPDATE products SET stock = stock - ? WHERE id = ?", quantity, productID)
    if err != nil {
        return err
    }

    _, err = tx.Exec("INSERT INTO orders (user_id, product_id, quantity) VALUES (?, ?, ?)", userID, productID, quantity)
    if err != nil {
        return err
    }

    return tx.Commit()
}

Here, FOR UPDATE locks the row with the product, preventing race conditions during parallel requests. The entire logic in the service is wrapped in a transaction, which ensures atomicity of the operation.