Ensure data consistency with database transactions
const { DB } = require("ilana-orm");
// Simple transaction with automatic management
await DB.transaction(async () => {
const user = await User.create({
name: "John Doe",
email: "john@example.com",
});
await user.profile().create({
bio: "Software developer",
});
await user.posts().create({
title: "My first post",
content: "Hello world!",
});
// All operations automatically use the same transaction
// Commits automatically if no errors, rolls back on exceptions
});
// Automatically retry on deadlock/serialization failures
await DB.transaction(async () => {
const order = await Order.create({
user_id: 1,
total: 99.99,
status: "pending",
});
await OrderItem.create({
order_id: order.id,
product_id: 1,
quantity: 2,
price: 49.99,
});
await Product.where("id", 1).decrement("stock_quantity", 2);
}, 3); // Retry up to 3 times
const trx = await DB.beginTransaction();
try {
const order = await Order.create({
user_id: 1,
total: 99.99,
status: "pending",
});
await OrderItem.create({
order_id: order.id,
product_id: 1,
quantity: 2,
price: 49.99,
});
await Product.where("id", 1).decrement("stock_quantity", 2);
await DB.commit();
console.log("Order created successfully");
} catch (error) {
await DB.rollback();
console.error("Order creation failed:", error);
throw error;
}
class User extends Model {
static async createWithProfile(userData, profileData) {
return await DB.transaction(async () => {
// Models automatically detect and use current transaction
const user = await this.create(userData);
const profile = await user.profile().create(profileData);
return { user, profile };
});
}
}
// Usage
const { user, profile } = await User.createWithProfile(
{ name: "John", email: "john@example.com" },
{ bio: "Developer", website: "john.dev" }
);
// When you need explicit control
const trx = await DB.beginTransaction();
try {
const user = await User.on(trx).create(userData);
const profile = await Profile.on(trx).create(profileData);
await DB.commit();
} catch (error) {
await DB.rollback();
throw error;
}
class Order extends Model {
static {
this.created(async (order) => {
await DB.transaction(async () => {
// Update inventory
for (const item of order.items) {
await Product.where("id", item.product_id).decrement(
"stock_quantity",
item.quantity
);
}
// Create audit log
await AuditLog.create({
action: "order_created",
order_id: order.id,
user_id: order.user_id,
});
});
});
}
}
// Transaction on specific connection
await DB.transaction(
async () => {
// Operations on analytics database
await AnalyticsEvent.create({ event: "user_signup" });
await UserMetrics.create({ user_id: 1, action: "signup" });
},
1,
"analytics_db"
);
// Manual transaction on specific connection
const trx = await DB.beginTransaction("postgres_analytics");
try {
await AnalyticsEvent.on("postgres_analytics").create({ event: "conversion" });
await DB.commit();
} catch (error) {
await DB.rollback();
throw error;
}
async function processBulkOrders(orders) {
return await DB.transaction(async () => {
const results = [];
// Process in batches to avoid memory issues
const batchSize = 100;
for (let i = 0; i < orders.length; i += batchSize) {
const batch = orders.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(async (orderData) => {
const order = await Order.create(orderData);
await OrderItem.insert(
orderData.items.map((item) => ({
...item,
order_id: order.id,
}))
);
return order;
})
);
results.push(...batchResults);
}
return results;
});
}
try {
await DB.transaction(async () => {
await User.create({ name: "John" });
// This will cause automatic rollback
throw new Error("Something went wrong");
// This won't execute
await User.create({ name: "Jane" });
});
} catch (error) {
console.log("Transaction rolled back:", error.message);
// No users were created
}
await DB.transaction(async () => {
const user = await User.create({
name: "John Doe",
email: "john@example.com",
});
try {
await sendWelcomeEmail(user.email);
} catch (emailError) {
console.log("Email failed, rolling back user creation");
throw emailError; // This will rollback the transaction
}
// Only commit if email was sent successfully
});
// ❌ Bad - long-running transaction
await DB.transaction(async () => {
const users = await User.all();
for (const user of users) {
// Expensive operation that takes time
await processUserData(user);
await user.update({ processed: true });
}
});
// ✅ Good - short transactions
const users = await User.all();
for (const user of users) {
await processUserData(user);
// Quick transaction just for the update
await DB.transaction(async () => {
await user.update({ processed: true });
});
}
// ✅ Good - batch operations in single transaction
await DB.transaction(async () => {
const order = await Order.create(orderData);
// Insert all items at once
await OrderItem.insert(
orderData.items.map((item) => ({
...item,
order_id: order.id,
}))
);
// Update all products at once
for (const item of orderData.items) {
await Product.where("id", item.product_id).decrement(
"stock_quantity",
item.quantity
);
}
});
async function transferMoney(fromAccountId, toAccountId, amount) {
return await DB.transaction(async () => {
// Lock accounts to prevent concurrent modifications
const fromAccount = await Account.where("id", fromAccountId)
.lockForUpdate()
.first();
const toAccount = await Account.where("id", toAccountId)
.lockForUpdate()
.first();
if (fromAccount.balance < amount) {
throw new Error("Insufficient funds");
}
// Perform transfer
await fromAccount.decrement("balance", amount);
await toAccount.increment("balance", amount);
// Create transaction record
await Transaction.create({
from_account_id: fromAccountId,
to_account_id: toAccountId,
amount: amount,
type: "transfer",
});
return { fromAccount, toAccount };
});
}
async function processOrder(orderData) {
return await DB.transaction(async () => {
const order = await Order.create({
user_id: orderData.user_id,
status: "processing",
});
let totalAmount = 0;
for (const item of orderData.items) {
const product = await Product.where("id", item.product_id)
.lockForUpdate()
.first();
if (product.stock_quantity < item.quantity) {
throw new Error(`Insufficient stock for ${product.name}`);
}
await product.decrement("stock_quantity", item.quantity);
await OrderItem.create({
order_id: order.id,
product_id: item.product_id,
quantity: item.quantity,
price: product.price,
});
totalAmount += product.price * item.quantity;
}
await order.update({
total: totalAmount,
status: "confirmed",
});
return order;
});
}
// ✅ Good - automatic management
await DB.transaction(async () => {
await User.create(userData);
await Profile.create(profileData);
});
// ❌ Avoid manual control unless necessary
const trx = await DB.beginTransaction();
try {
await User.create(userData);
await Profile.create(profileData);
await DB.commit();
} catch (error) {
await DB.rollback();
throw error;
}
// ✅ Good - retry on deadlocks
await DB.transaction(async () => {
await processPayment(paymentData);
await updateInventory(items);
}, 3); // Retry up to 3 times
// ✅ Good
await DB.transaction(async () => {
const user = await User.create(userData);
await Profile.create(profileData);
});
// Send email after transaction commits
await sendWelcomeEmail(user.email);
// ❌ Bad - includes external API call
await DB.transaction(async () => {
const user = await User.create(userData);
await sendWelcomeEmail(user.email); // External API call
await Profile.create(profileData);
});
// ❌ Unnecessary transaction
await DB.transaction(async () => {
const users = await User.all(); // No need for transaction
return users;
});
// ✅ Good - no transaction needed
const users = await User.all();