Core Patterns
This guide documents canonical usage patterns for the Go TableTheory SDK, designed to be copy-pasted into your application.
TypeScript and Python ship their own runtime-specific core-pattern guides as sibling package surfaces in the shared TheoryCloud TableTheory subtree. This page is the Go patterns guide.
Lambda Optimization
Problem: AWS Lambda functions suffer from “cold starts” if connections are re-established on every invocation.
Solution: Use NewLambdaOptimized in the global scope or init() function.
// ✅ CORRECT: Global initialization
var db *tabletheory.LambdaDB
func init() {
var err error
// Initialize once during cold start
db, err = tabletheory.NewLambdaOptimized()
if err != nil {
panic(err)
}
}
func handler(ctx context.Context) error {
// db is ready to use immediately
return db.Model(&User{}).Create()
}
When a function needs a shorter cleanup window than the default 1 second buffer, configure that buffer at cold start and derive an invocation-scoped DB from the Lambda context inside the handler:
// ✅ CORRECT: configure once, derive per invocation
var db *tabletheory.LambdaDB
func init() {
base, err := tabletheory.LambdaInit(&User{})
if err != nil {
panic(err)
}
db = base.WithLambdaTimeoutConfig(tabletheory.LambdaTimeoutConfig{
Buffer: 500 * time.Millisecond,
})
}
func handler(ctx context.Context) error {
invocationDB := db.WithLambdaTimeout(ctx)
return invocationDB.Model(&User{}).Create()
}
// ❌ INCORRECT: Handler initialization
func handler(ctx context.Context) error {
db, _ := tabletheory.New(...) // Re-connects every time! 10x slower.
return db.Model(&User{}).Create()
}
Pagination
Problem: Retrieving large datasets in a single call can exceed DynamoDB limits (1MB) or timeout.
Solution: Use Limit() and loop until results are exhausted.
// ✅ CORRECT: Paginated Query
var allUsers []User
lastEvaluatedKey := ""
for {
var page []User
// Configure query
q := db.Model(&User{}).Limit(50)
// Apply cursor if continuing
if lastEvaluatedKey != "" {
q.Cursor(lastEvaluatedKey)
}
// Fetch page
result, err := q.AllPaginated(&page)
if err != nil {
log.Fatal(err)
}
allUsers = append(allUsers, page...)
// Check if more pages exist
if !result.HasMore {
break
}
lastEvaluatedKey = result.NextCursor
}
Optimistic Locking (Versioning)
Problem: Two users update the same item simultaneously, overwriting each other’s changes.
Solution: Use a version field and AtVersion condition.
- Model Setup: Add a version field.
type Document struct {
ID string `theorydb:"pk"`
Content string
Version int64 `theorydb:"version"` // Automatically increments on update
}
- Update Logic:
// ✅ CORRECT: Guarded Update
doc := &Document{ID: "doc_1", Content: "New Content", Version: 5}
// Fails if current version in DB is not 5
err := db.Model(doc).
Where("ID", "=", doc.ID).
WithCondition("Version", "=", 5). // Or use .AtVersion(5) in Transaction
Update()
if errors.Is(err, customerrors.ErrConditionFailed) {
// Handle conflict: Fetch latest and retry
}
Batch Operations
Problem: Reading items one by one is slow and inefficient.
Solution: Use BatchGet to fetch up to 100 items in parallel.
// ✅ CORRECT: Batch retrieval
var users []User
// Define keys to fetch
keys := []User{
{ID: "1"},
{ID: "2"},
{ID: "3"},
}
// Pass slice of structs with keys set
err := db.Model(&User{}).BatchGet(keys, &users)
DynamoDB Streams
Problem: Processing stream events requires parsing complex DynamoDB JSON.
Solution: Use tabletheory.UnmarshalStreamImage to convert stream images to your models.
// ✅ CORRECT: Stream processing
func handleStream(e events.DynamoDBEvent) {
for _, record := range e.Records {
if record.EventName == "INSERT" || record.EventName == "MODIFY" {
var user User
// Convert Lambda Event Map -> Go Struct
err := tabletheory.UnmarshalStreamImage(record.Change.NewImage, &user)
if err != nil {
log.Println("Parse error:", err)
continue
}
processUser(user)
}
}
}
Atomic Transactions
Problem: Need to update multiple items atomically (e.g., bank transfer).
Solution: Use db.TransactWrite for ACID guarantees.
// ✅ CORRECT: Transaction
err := db.TransactWrite(ctx, func(tx core.TransactionBuilder) error {
// 1. Deduct from Sender
tx.Update(sender, []string{"Balance"})
// 2. Add to Receiver
tx.Update(receiver, []string{"Balance"})
// 3. Record Audit Log
tx.Put(auditLog)
return nil
})
// If ANY operation fails, EVERYTHING is rolled back.
Conditional Writes
Problem: Prevent overwriting data if it already exists (idempotency).
Solution: Use .IfNotExists() or .Where() conditions on writes.
// ✅ CORRECT: Insert only if ID doesn't exist
err := db.Model(&User{ID: "123"}).
IfNotExists().
Create()
if errors.Is(err, customerrors.ErrConditionFailed) {
log.Println("User already exists!")
}
Conditional Writes
Problem: Prevent overwriting data if it already exists (idempotency).
Solution: Use .IfNotExists() or .Where() conditions on writes.
// ✅ CORRECT: Insert only if ID doesn't exist
err := db.Model(&User{ID: "123"}).
IfNotExists().
Create()
if errors.Is(err, customerrors.ErrConditionFailed) {
log.Println("User already exists!")
}
Business Value & Use Cases
TableTheory is designed to provide significant business value by improving developer efficiency, reducing operational costs, and enhancing application performance and reliability.
Developer Efficiency & Team Velocity
- Reduced Boilerplate: TableTheory eliminates approximately 80% of the boilerplate code typically required for DynamoDB interactions with the raw AWS SDK. This frees developers to focus on business logic.
- Type Safety: Compile-time type safety with Go generics prevents common runtime errors, leading to fewer bugs and faster development cycles.
- Intuitive API: The fluent, chainable API makes code more readable and easier to maintain, reducing the learning curve for new team members.
Performance & Reliability
- Sub-15ms Cold Starts: With Lambda-optimized initialization (
NewLambdaOptimized,LambdaInit), TableTheory achieves 91% faster cold starts compared to raw AWS SDK usage, crucial for responsive serverless applications. - Optimized Memory Usage: TableTheory uses 57% less memory in Lambda environments, contributing to lower execution costs and fewer memory-related issues.
- Production-Ready Patterns: Built-in support for transactions, conditional writes, and retry logic helps build robust and fault-tolerant applications.
Cost Optimization
- Reduced RCUs/WCUs: By promoting efficient querying (avoiding scans, using indexes correctly) and batch operations, TableTheory helps minimize consumed Read Capacity Units (RCUs) and Write Capacity Units (WCUs), directly lowering DynamoDB costs.
- Lower Lambda Costs: Faster cold starts and reduced memory footprint mean Lambda functions run for shorter durations and require less memory, leading to lower compute costs.
- Faster Development = Lower Project Costs: Increased team velocity translates to projects delivered faster and with fewer resources.
Primary Use Cases
TableTheory is ideal for:
- Serverless Backends: Building highly scalable and performant APIs with AWS Lambda and API Gateway.
- Event-Driven Architectures: Processing DynamoDB Streams with type-safe model transformations.
- High-Throughput Microservices: Services requiring fast, efficient interactions with DynamoDB.
- Financial & Critical Systems: Leveraging atomic transactions for data consistency.
- Real-time Data Processing: Applications needing low-latency access to DynamoDB data.
Performance & Cost Optimization
Problem: Unoptimized DynamoDB interactions or Lambda configurations can lead to high costs and slow performance. Solution: Implement strategies for Lambda memory sizing, efficient connection pooling, and careful RCU/WCU management.
Lambda Memory Sizing
Principle: Higher Lambda memory often means more CPU, faster execution, and lower overall cost for compute-bound tasks, despite a higher per-GB-second price.
- Recommendation: Start with 512MB-1GB for most TableTheory-based Lambda functions. Monitor CPU time and memory usage (using
LambdaDB.GetMemoryStats()) to fine-tune. - Impact:
- Memory <= 256MB: May incur higher cold starts due to limited CPU.
- Memory >= 1024MB: Generally provides optimal performance for typical workloads.
Example (Monitoring Memory Usage):
// ✅ CORRECT: Logging memory stats for optimization
func handler(ctx context.Context) error {
// ... your business logic ...
stats := db.GetMemoryStats()
log.Printf("Lambda Memory Stats: Alloc: %.2fMB, Sys: %.2fMB, Used: %.2f%%",
stats.AllocatedMB, stats.SystemMB, stats.MemoryPercent)
// Use these stats to adjust Lambda memory configuration in your CDK/CloudFormation
return nil
}
Connection Pooling & Reuse
Principle: Reusing HTTP connections and DynamoDB clients across Lambda invocations drastically reduces cold start latency.
- Recommendation: Always use
tabletheory.NewLambdaOptimized()ortabletheory.LambdaInit()in yourinit()function or global scope. - Details: TableTheory’s
LambdaDBmanages an optimizedhttp.Clientwith appropriateMaxIdleConnsandIdleConnTimeoutsettings for Lambda’s execution model. - Timeout buffer: Use
LambdaDB.WithLambdaTimeoutConfig(tabletheory.LambdaTimeoutConfig{Buffer: ...})during cold start to customize the buffer left before the Lambda hard deadline. Continue to calldb.WithLambdaTimeout(ctx)per invocation.
Example:
// ✅ CORRECT: Global init ensures connection pooling
var db *tabletheory.LambdaDB
func init() {
db, _ = tabletheory.NewLambdaOptimized()
db.OptimizeForMemory() // Auto-adjusts internal buffers
}
Read/Write Capacity Unit (RCU/WCU) Management
Principle: Minimize consumed capacity by avoiding full table scans and using efficient access patterns.
- Recommendation:
- Avoid Scans: Unless absolutely necessary for infrequent analytics on small tables, never use
Scan()for primary access patterns. Always preferQuery()with appropriate Partition and Sort Key conditions. - Batch Operations: Use
BatchGet,BatchCreate,BatchDeletefor multiple items to reduce network overhead and potentially consumed capacity compared to individual operations. - Consistent Reads: Only enable
ConsistentRead()when strong consistency is strictly required, as it consumes 2x RCUs. - GSI Projection: Use
KEYS_ONLYorINCLUDEprojections on GSIs to reduce the size of items read from the index, minimizing RCU consumption.
- Avoid Scans: Unless absolutely necessary for infrequent analytics on small tables, never use
Example (Efficient Query vs. Scan):
// ❌ INCORRECT: Expensive full table scan
// Will consume RCU proportional to table size, not result size
db.Model(&Product{}).Where("Category", "=", "electronics").All(&products)
// ✅ CORRECT: Efficient GSI query
// Assumes a GSI 'category-index' with Category as its PK
db.Model(&Product{}).Index("category-index").Where("Category", "=", "electronics").All(&products)
Problem: Prevent overwriting data if it already exists (idempotency).
Solution: Use .IfNotExists() or .Where() conditions on writes.
// ✅ CORRECT: Insert only if ID doesn't exist
err := db.Model(&User{ID: "123"}).
IfNotExists().
Create()
if errors.Is(err, customerrors.ErrConditionFailed) {
log.Println("User already exists!")
}