Skip to main content

Anti-Patterns Guide

This guide documents common mistakes and anti-patterns to avoid when developing with the MBC CQRS Serverless framework. Understanding what NOT to do is as important as knowing the best practices.

Command Handling Anti-Patterns

AP001: Direct Database Writes Bypassing Command Service

Anti-Pattern

Never write directly to DynamoDB tables bypassing the CommandService.

// ❌ Anti-Pattern: Direct DynamoDB write
const dynamodb = new DynamoDBClient({});
await dynamodb.send(new PutItemCommand({
TableName: 'my-table',
Item: { pk: { S: 'TENANT#mbc' }, sk: { S: 'ITEM#001' }, ... }
}));
// ✅ Correct: Use CommandService
await this.commandService.publishAsync(new ItemCommand({
pk: { S: 'TENANT#mbc' },
sk: { S: 'ITEM#001' },
...
}), context);

Why this is problematic:

  • Bypasses version control and optimistic locking
  • Skips event publishing for downstream sync
  • No audit trail in command table
  • Breaks CQRS pattern consistency

AP002: Ignoring Version Mismatch Errors

Anti-Pattern

Never catch and ignore VersionMismatchError without proper handling.

// ❌ Anti-Pattern: Silently ignoring version mismatch
try {
await this.commandService.publishAsync(command, context);
} catch (error) {
if (error.name === 'VersionMismatchError') {
console.log('Version mismatch, skipping...'); // Silent failure!
}
}
// ✅ Correct: Implement retry with fresh data
const MAX_RETRIES = 3;
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
const latest = await this.dataService.getItem({ pk, sk });
const command = this.buildCommand(latest);
await this.commandService.publishAsync(command, context);
break;
} catch (error) {
if (error.name === 'VersionMismatchError' && attempt < MAX_RETRIES - 1) {
continue; // Retry with fresh data
}
throw error;
}
}

Why this is problematic:

  • Data loss - changes are silently discarded
  • Inconsistent state between what user sees and database
  • Hard to debug issues in production

AP003: Using publishSync for Heavy Operations

Anti-Pattern

Avoid publishSync for operations that trigger heavy downstream processing.

// ❌ Anti-Pattern: Sync publish for batch import
for (const item of thousandItems) {
await this.commandService.publishSync(item, context); // Blocks until complete!
}
// ✅ Correct: Use publishAsync for batch operations
const promises = thousandItems.map(item =>
this.commandService.publishAsync(item, context)
);
await Promise.all(promises);

Why this is problematic:

  • Lambda timeout risk - Step Functions execution adds latency
  • Poor user experience - long wait times
  • Higher costs - Lambda billed by duration

Data Access Anti-Patterns

AP004: N+1 Query Pattern

Anti-Pattern

Avoid fetching related data inside loops.

// ❌ Anti-Pattern: N+1 queries
const orders = await this.dataService.listItems({ pk: tenantPk });
for (const order of orders) {
// Each iteration makes a DB call!
const customer = await this.dataService.getItem({
pk: order.customerPk,
sk: order.customerSk
});
order.customer = customer;
}
// ✅ Correct: Batch fetch or denormalize
const orders = await this.dataService.listItems({ pk: tenantPk });
const customerKeys = orders.map(o => ({ pk: o.customerPk, sk: o.customerSk }));
const customers = await this.dataService.batchGetItems(customerKeys);
const customerMap = new Map(customers.map(c => [c.sk, c]));
orders.forEach(order => {
order.customer = customerMap.get(order.customerSk);
});

Why this is problematic:

  • Performance degradation - 100 orders = 101 DB calls
  • Higher DynamoDB costs
  • Potential throttling under load

AP005: Scanning Without Filters

Anti-Pattern

Never scan entire tables without proper filtering.

// ❌ Anti-Pattern: Full table scan
const allItems = await this.dataService.scan({ TableName: 'data-table' });
const filteredItems = allItems.filter(item => item.status === 'active');
// ✅ Correct: Query with proper key conditions
const activeItems = await this.dataService.listItems({
pk: tenantPk,
sk: { $beginsWith: 'ITEM#' },
filter: { status: 'active' }
});

Why this is problematic:

  • Reads entire table consuming massive RCUs
  • Extremely slow for large tables
  • Can cause DynamoDB throttling affecting other operations

AP006: Storing Large Objects in DynamoDB

Anti-Pattern

Don't store large files or binary data directly in DynamoDB items.

// ❌ Anti-Pattern: Large base64 file in DynamoDB
const command = new DocumentCommand({
pk,
sk,
attributes: {
pdfContent: largeBase64String, // Could be megabytes!
}
});
// ✅ Correct: Store in S3, reference in DynamoDB
const s3Key = `documents/${tenantCode}/${documentId}.pdf`;
await this.s3Service.upload(s3Key, fileBuffer);

const command = new DocumentCommand({
pk,
sk,
attributes: {
pdfLocation: toS3Uri(bucket, s3Key), // s3://bucket/path
}
});

Why this is problematic:

  • DynamoDB item size limit is 400KB
  • High read/write costs for large items
  • Slower query performance

Multi-Tenant Anti-Patterns

AP007: Hardcoding Tenant Code

Anti-Pattern

Never hardcode tenant codes in application logic.

// ❌ Anti-Pattern: Hardcoded tenant
const pk = 'TENANT#default';
const items = await this.dataService.listItems({ pk });
// ✅ Correct: Use context-provided tenant
const { tenantCode } = getUserContext(context);
const pk = generatePk(tenantCode);
const items = await this.dataService.listItems({ pk });

Why this is problematic:

  • Cross-tenant data leakage risk
  • Breaks multi-tenant isolation
  • Difficult to debug tenant-specific issues

AP008: Missing Tenant Validation

Anti-Pattern

Never trust client-provided tenant codes without validation.

// ❌ Anti-Pattern: Trusting client input
@Post()
async create(@Body() dto: CreateDto) {
const pk = `TENANT#${dto.tenantCode}`; // Client controls tenant!
// ...
}
// ✅ Correct: Validate against JWT claims
@Post()
async create(
@Body() dto: CreateDto,
@Req() request: IInvoke
) {
const { tenantCode } = getUserContext(request);
const pk = generatePk(tenantCode); // From authenticated context
// ...
}

Why this is problematic:

  • Critical security vulnerability
  • Attackers can access other tenants' data
  • Compliance violations (GDPR, SOC2, etc.)

Event Handling Anti-Patterns

AP009: Throwing Errors in Data Sync Handlers

Anti-Pattern

Don't let unhandled exceptions escape from DataSyncHandler.

// ❌ Anti-Pattern: Unhandled exception
@DataSyncHandler({ tableName: 'data-table' })
export class MyDataSyncHandler implements IDataSyncHandler {
async handleSync(event: SyncEvent): Promise<void> {
const result = await this.externalApi.call(event.data);
// If API fails, entire batch fails and retries!
}
}
// ✅ Correct: Handle errors gracefully
@DataSyncHandler({ tableName: 'data-table' })
export class MyDataSyncHandler implements IDataSyncHandler {
async handleSync(event: SyncEvent): Promise<void> {
try {
const result = await this.externalApi.call(event.data);
} catch (error) {
this.logger.error('Sync failed', { event, error });
await this.deadLetterQueue.send(event); // DLQ for later processing
// Don't rethrow - mark as processed
}
}
}

Why this is problematic:

  • DynamoDB Streams retries the entire batch
  • Can cause infinite retry loops
  • Blocks processing of subsequent events

AP010: Long-Running Sync Handlers

Anti-Pattern

Avoid long-running operations in sync handlers.

// ❌ Anti-Pattern: Heavy processing in handler
@DataSyncHandler({ tableName: 'data-table' })
export class MyDataSyncHandler implements IDataSyncHandler {
async handleSync(event: SyncEvent): Promise<void> {
await this.generatePdfReport(event.data); // Takes 30+ seconds
await this.sendEmailWithAttachment(report);
}
}
// ✅ Correct: Delegate to async processing
@DataSyncHandler({ tableName: 'data-table' })
export class MyDataSyncHandler implements IDataSyncHandler {
async handleSync(event: SyncEvent): Promise<void> {
// Quick enqueue, process asynchronously
await this.taskService.publish('GenerateReport', {
itemId: event.data.id,
type: 'pdf'
});
}
}

Why this is problematic:

  • Lambda timeout (15 min max)
  • Blocks DynamoDB Stream processing
  • Higher costs and poor scalability

Quick Reference Card

CodeAnti-PatternSeverity
AP001Direct database writesCritical
AP002Ignoring version mismatchHigh
AP003publishSync for heavy opsMedium
AP004N+1 query patternHigh
AP005Unfiltered table scansHigh
AP006Large objects in DynamoDBMedium
AP007Hardcoded tenant codesCritical
AP008Missing tenant validationCritical
AP009Throwing in sync handlersHigh
AP010Long-running sync handlersMedium

Planned Detection (Future Releases)

Note

The following anti-patterns are documented for reference but automated detection is not yet implemented in the MCP server. Detection for these patterns is planned for future releases.

CodeAnti-PatternSeverityStatus
AP011No sequence exhaustion handlingLowPlanned
AP012Sequences for non-sequential IDsLowPlanned
AP013Secrets in codeCriticalPlanned
AP014Unvalidated JWT claimsCriticalPlanned
AP015Testing against productionCriticalPlanned
AP016Real AWS calls in testsMediumPlanned
AP017Cold start amplificationMediumPlanned
AP018Unbounded batch operationsHighPlanned

See Also