Inventario sin vender de más

TeselaDB

TeselaDB

Equipo de TeselaDB

30 minIntermedio

Descuenta el stock y crea el pedido en una sola transacción para que dos compras a la vez nunca vendan la misma unidad.

Cuando varios clientes compran el último producto a la vez, cada compra tiene que descontar el stock y crear su pedido sin pisar a las demás. Con una transacción multidocumento las dos escrituras ocurren juntas o no ocurre ninguna, tanto si el stock y los pedidos están en el mismo rango como si están en rangos de nodos distintos.

Lo que vas a construir

Una función placeOrder de Node.js que descuenta unidades solo si quedan y crea el pedido en la misma transacción. Cinco compras a la vez de un producto con tres unidades dejan tres pedidos y el stock a cero.

Requisitos

  • Node.js 18 o posterior con el paquete mongodb.
  • Un clúster de TeselaDB y un usuario con readWrite sobre la base shop.
  • La cadena de conexión en la variable de entorno TESELADB_URI.

1. Descuenta con una condición

El filtro available: { $gte: quantity } hace que la actualización solo cambie el documento si quedan unidades suficientes. Si no cambia nada, la función lanza un error y la transacción se deshace entera.

2. Escribe la compra en una transacción

withTransaction reintenta la transacción cuando choca con otra que tocó el mismo documento, así que no tienes que escribir los reintentos. Guarda el programa como inventario.mjs:

JavaScript
import { MongoClient } from "mongodb";

const client = new MongoClient(process.env.TESELADB_URI);
const db = client.db("shop");

// Takes the units from the stock and writes the order in one transaction: both happen, or neither.
async function placeOrder(sku, quantity, customer) {
  const session = client.startSession();
  try {
    return await session.withTransaction(async () => {
      const taken = await db
        .collection("stock")
        .updateOne({ _id: sku, available: { $gte: quantity } }, { $inc: { available: -quantity } }, { session });
      if (taken.modifiedCount === 0) throw new Error(`not enough stock for ${sku}`);
      await db.collection("orders").insertOne({ sku, quantity, customer, createdAt: new Date() }, { session });
    });
  } finally {
    await session.endSession();
  }
}

await db.collection("stock").insertOne({ _id: "chair-red", available: 3 });
const customers = ["Ana", "Luis", "Marta", "Pablo", "Sara"];
const results = await Promise.allSettled(customers.map((customer) => placeOrder("chair-red", 1, customer)));
const placed = results.filter((result) => result.status === "fulfilled").length;
const { available } = await db.collection("stock").findOne({ _id: "chair-red" });
console.log(`${placed} orders placed, ${available} left`);
await client.close();

Probarlo

Ejecuta node inventario.mjs contra una base vacía. Las cinco compras van a la vez, tres encuentran stock y dos fallan con not enough stock:

3 orders placed, 0 left

Limpiar

JavaScript
db.getSiblingDB("shop").stock.deleteOne({ _id: "chair-red" });
db.getSiblingDB("shop").orders.deleteMany({ sku: "chair-red" });

Desde mongosh, la primera orden borra el producto de prueba y la segunda borra sus pedidos.

Siguientes pasos