Batch Context Configuration
DynamoBao provides flexible batch context behavior that can be configured based on your application's needs. This tutorial covers how to configure and use the batch context system for optimal performance and safety.
Overview
The batch context system in DynamoBao enables:
- Efficient batching: Multiple database operations are combined into fewer requests
- Automatic caching: Results are cached within the same request context
- Request isolation: Ensures data doesn't leak between concurrent requests
- Flexible enforcement: Configure whether batch context is required or optional
Configuration Options
Add the batchContext
configuration to your config file:
// config.js or dynamo-bao.config.js
module.exports = {
aws: {
region: "us-west-2",
},
db: {
tableName: "your-table-name",
},
batchContext: {
requireBatchContext: false, // Default: allow fallback behavior
},
// ... other config
};
Behavior Modes
Default Mode (requireBatchContext: false
)
Best for: Production environments, gradual migration, maximum flexibility
const manager = initModels({
batchContext: { requireBatchContext: false },
});
// Works with direct execution (no batching/caching)
const user = await User.find("user123");
// Also works with batching + caching
await runWithBatchContext(async () => {
const user = await User.find("user123");
});
Behavior:
- Operations inside
runWithBatchContext
: Full batching and caching enabled - Operations outside
runWithBatchContext
: Direct execution without batching or caching - No errors thrown, maximum backward compatibility
Strict Mode (requireBatchContext: true
)
Best for: Development, testing, ensuring consistent batch context usage
const manager = initModels({
batchContext: { requireBatchContext: true },
});
// This throws an error
await User.find("user123");
// Error: Batch operations must be executed within runWithBatchContext()
// This works
await runWithBatchContext(async () => {
const user = await User.find("user123"); // ✅
});
Behavior:
- Operations inside
runWithBatchContext
: Full batching and caching enabled - Operations outside
runWithBatchContext
: Throws an error - Ensures all database operations use proper batch context
Context Detection API
You can check if code is currently running within a batch context:
const { User } = require("./models/user");
// Check if inside batch context
const isInBatchContext = User.isInsideBatchContext();
if (isInBatchContext) {
console.log("Running with batching enabled");
// Can use advanced batching features
} else {
console.log("Running in direct execution mode");
// Operations will be direct database calls
}
Environment Variable Support
Configure batch context behavior via environment variables:
# Enable strict mode globally
export DYNAMO_BAO_REQUIRE_BATCH_CONTEXT=true
# Your application will now require runWithBatchContext for all operations
node your-app.js
This is particularly useful for:
- Development/Testing: Use strict mode to catch missing batch contexts early
- Production: Use default mode for maximum flexibility
- CI/CD: Different environments can have different enforcement levels
Usage Patterns
Web Application with Express.js
const express = require("express");
const { runWithBatchContext, initModels } = require("dynamo-bao");
const app = express();
// Initialize with development-friendly strict mode
const manager = initModels({
batchContext: {
requireBatchContext: process.env.NODE_ENV === "development",
},
});
// Middleware to wrap requests in batch context
app.use((req, res, next) => {
runWithBatchContext(() => {
next();
});
});
// Routes automatically benefit from batching
app.get("/api/users/:id/dashboard", async (req, res) => {
const { User, Post } = manager.models;
// These operations will be batched and cached
const user = await User.find(req.params.id);
const posts = await user.queryPosts();
// Load related data efficiently
await Promise.all(
posts.items.map((post) => post.loadRelatedData(["userId"])),
);
res.json({ user, posts: posts.items });
});
AWS Lambda Functions
const { runWithBatchContext, initModels } = require("dynamo-bao");
// Configure based on environment
const manager = initModels({
batchContext: {
requireBatchContext: process.env.STAGE === "dev", // Strict in dev, flexible in prod
},
});
exports.handler = async (event) => {
return runWithBatchContext(async () => {
const { User, Order } = manager.models;
// Efficient batch operations
const userId = event.pathParameters.userId;
const user = await User.find(userId);
const orders = await user.queryOrders();
return {
statusCode: 200,
body: JSON.stringify({ user, orders: orders.items }),
};
});
};
Background Jobs
const { runWithBatchContext } = require("dynamo-bao");
async function processUserEmails() {
await runWithBatchContext(async () => {
const { User } = manager.models;
// Iterate through users efficiently
for await (const batch of User.iterateAll({ batchSize: 100 })) {
await Promise.all(
batch.map(async (user) => {
if (user.emailPreferences.notifications) {
await sendNotificationEmail(user);
}
}),
);
}
});
}
Migration Strategies
Gradual Migration from Direct Calls
Step 1: Start with default mode (no errors)
// config.js
module.exports = {
batchContext: { requireBatchContext: false }, // Start permissive
};
Step 2: Wrap critical paths
// Start with high-traffic endpoints
app.get("/api/dashboard", async (req, res) => {
await runWithBatchContext(async () => {
// Your existing code here - now with batching!
});
});
Step 3: Enable detection and monitoring
app.use((req, res, next) => {
const isInBatch = User.isInsideBatchContext();
if (!isInBatch) {
console.warn(`Route ${req.path} not using batch context`);
}
next();
});
Step 4: Gradually enable strict mode
// config.js
module.exports = {
batchContext: {
requireBatchContext: process.env.NODE_ENV === "development",
},
};
Performance Considerations
With Batch Context (Recommended)
await runWithBatchContext(async () => {
// These 10 finds become 1 DynamoDB BatchGet operation
const users = await Promise.all([
User.find("user1"),
User.find("user2"),
// ... 8 more
]);
// Subsequent finds for same users return cached instances
const user1Again = await User.find("user1"); // Returns same object
expect(user1Again).toBe(users[0]); // Same object reference
});
Without Batch Context (Direct Mode)
// These become 10 separate DynamoDB Get operations
const users = await Promise.all([
User.find("user1"), // Individual DynamoDB call
User.find("user2"), // Individual DynamoDB call
// ... 8 more individual calls
]);
// No caching - each find creates a new request
const user1Again = await User.find("user1"); // Another DynamoDB call
expect(user1Again).not.toBe(users[0]); // Different object instances
Best Practices
- Use strict mode in development to catch missing batch contexts early
- Use default mode in production for maximum deployment flexibility
- Always wrap request handlers with
runWithBatchContext
for optimal performance - Monitor batch context usage in production to identify optimization opportunities
- Use environment variables to configure behavior per environment
- Gradually migrate existing codebases using the permissive default mode
Error Handling
When requireBatchContext: true
, operations outside batch context throw descriptive errors:
try {
await User.find("user123"); // Outside batch context
} catch (error) {
console.log(error.message);
// "Batch operations must be executed within runWithBatchContext().
// Wrap your database operations in runWithBatchContext() to enable batching and caching."
}
Integration with Other Features
Multi-Tenancy
Batch context works seamlessly with multi-tenancy:
await TenantContext.runWithTenant("tenant-123", async () => {
await runWithBatchContext(async () => {
// Operations are both tenant-scoped AND batched
const users = await User.queryByIndex("byStatus", "active");
});
});
Testing
Batch context is perfect for test isolation:
// Each test gets its own isolated batch context
test("user creation", async () => {
await runWithBatchContext(async () => {
const user = await User.create({ name: "Test User" });
expect(user.name).toBe("Test User");
});
});