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: 'mbc#ITEM' }, sk: { S: 'ITEM#001' }, ... }
}));
// ✅ Correct: Use CommandService
await this.commandService.publishAsync({
pk: 'mbc#ITEM',
sk: 'ITEM#001',
version: 0,
// ...
}, { invokeContext });

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 ConditionalCheckFailedException without proper handling.

// ❌ Anti-Pattern: Silently ignoring version mismatch
import { ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb';

try {
await this.commandService.publishAsync(command, context);
} catch (error) {
if (error instanceof ConditionalCheckFailedException) {
console.log('Version mismatch, skipping...'); // Silent failure!
}
}
// ✅ Correct: Implement retry with fresh data
import { ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb';

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 instanceof ConditionalCheckFailedException && 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 = 'default#PRODUCT';
const items = await this.dataService.listItems({ pk });
// ✅ Correct: Use context-provided tenant
const { tenantCode } = getUserContext(context);
const pk = `${tenantCode}#PRODUCT`; // Build PK: tenantCode#entityType
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 = `${dto.tenantCode}#PRODUCT`; // Client controls tenant!
// ...
}
// ✅ Correct: Validate against JWT claims
@Post()
async create(
@Body() dto: CreateDto,
@Req() request: IInvoke
) {
const { tenantCode } = getUserContext(request);
const pk = `${tenantCode}#PRODUCT`; // 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('sample')
export class MyDataSyncHandler implements IDataSyncHandler {
async up(cmd: CommandModel): Promise<any> {
const result = await this.externalApi.call(cmd.attributes);
// If API fails, entire batch fails and retries!
}
}
// ✅ Correct: Handle errors gracefully
@DataSyncHandler('sample')
export class MyDataSyncHandler implements IDataSyncHandler {
async up(cmd: CommandModel): Promise<any> {
try {
const result = await this.externalApi.call(cmd.attributes);
} catch (error) {
this.logger.error('Sync failed', { cmd, error });
await this.deadLetterQueue.send(cmd); // 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('sample')
export class MyDataSyncHandler implements IDataSyncHandler {
async up(cmd: CommandModel): Promise<any> {
await this.generatePdfReport(cmd.attributes); // Takes 30+ seconds
await this.sendEmailWithAttachment(report);
}
}
// ✅ Correct: Delegate to async processing
@DataSyncHandler('sample')
export class MyDataSyncHandler implements IDataSyncHandler {
async up(cmd: CommandModel): Promise<any> {
// Quick enqueue, process asynchronously
await this.taskService.publish('GenerateReport', {
itemId: cmd.id,
type: 'pdf'
});
}
}

Why this is problematic:

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

Quick Reference Card

The MCP server currently detects the following anti-patterns automatically. For full details on each code, see MCP Server Anti-Pattern Detection.

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
AP011Deprecated publish() methodHigh
AP012Uppercase COMMON tenant key (pre-v1.1.0)Critical
AP013publishSync null return unchecked (v1.2.0+)High
AP014Deprecated genNewSequence (v1.2.0)High
AP015Duplicate TaskModule.register()High
AP016Missing error logging before rethrowHigh
AP017Incorrect attribute merging on partial updateHigh
AP018Missing Swagger documentationLow
AP019Missing pagination in list queriesHigh
AP020Missing getCommandSource for tracingLow
AP021Event emit directly after publishAsyncHigh
AP022Use of eval() or Function() constructorCritical
AP023Shell command built from string concatenationCritical
AP024HTTP request without timeoutMedium
AP025Logging process.env or full request objectHigh