| name | mbc-review |
| description | Review code for MBC CQRS Serverless best practices and anti-patterns. Use this when reviewing code that uses MBC CQRS Serverless framework, checking for common mistakes, or validating implementation patterns. |
Pre-flight Check (Version Update)
Before executing this skill, check for updates:
- Run
mbc install-skills --check to check if a newer version is available
- If the output shows "Update available: X.Y.Z → A.B.C", ask the user:
- "A newer version of MBC skills is available (X.Y.Z → A.B.C). Would you like to update before proceeding?"
- If the user agrees, run
mbc install-skills --force to update
- If the user declines or skills are up-to-date, proceed with the skill
Note: Skip this check if the user explicitly says to skip updates or if you've already checked in this session.
MBC CQRS Serverless Code Review
This skill reviews code for MBC CQRS Serverless best practices and identifies anti-patterns.
Anti-Patterns to Detect
AP001: Using publishSync Instead of publishAsync
Severity: Warning
Pattern:
await this.commandService.publishSync(command, options);
await this.commandService.publishAsync(command, options);
Explanation: publishAsync is the recommended default. Only use publishSync when immediate consistency is absolutely required, as it blocks until processing completes.
AP002: Missing tenantCode in Multi-Tenant Operations
Severity: Error
Pattern:
const pk = `ORDER#${code}`;
const { tenantCode } = getUserContext(invokeContext);
const pk = `ORDER#${tenantCode}`;
Explanation: All operations must include tenantCode for proper tenant isolation.
AP003: Hardcoded Version Numbers
Severity: Error
Pattern:
version: 1,
version: 0,
version: VERSION_FIRST,
version: existingItem.version,
Explanation: Use VERSION_FIRST constant for new entities. For updates, always fetch the current version to enable optimistic locking.
AP004: Missing DataSyncHandler Registration
Severity: Error
Pattern:
CommandModule.register({
tableName: 'order',
}),
CommandModule.register({
tableName: 'order',
dataSyncHandlers: [OrderDataSyncRdsHandler],
}),
Explanation: If you have DataSyncHandlers, they must be registered in the module.
AP005: Not Handling ConditionalCheckFailedException
Severity: Warning
Pattern:
await this.commandService.publishAsync(command, options);
try {
await this.commandService.publishAsync(command, options);
} catch (error) {
if (error.name === 'ConditionalCheckFailedException') {
throw new ConflictException('Data was modified by another user');
}
throw error;
}
Explanation: Optimistic locking can cause version conflicts that should be handled gracefully.
AP006: Using Wrong PK/SK Format
Severity: Error
Pattern:
const pk = `order-${tenantCode}`;
const sk = `order_${code}`;
const pk = `ORDER#${tenantCode}`;
const sk = `ORDER#${code}`;
Explanation: Follow the ENTITY#value convention for partition and sort keys.
AP007: Missing invokeContext in Service Methods
Severity: Error
Pattern:
async create(dto: CreateOrderDto) {
}
async create(dto: CreateOrderDto, invokeContext: IInvoke) {
const { tenantCode, userId } = getUserContext(invokeContext);
}
Explanation: invokeContext is required to extract user information and tenant context.
AP008: Not Using generateId for Entity IDs
Severity: Warning
Pattern:
id: uuid(),
id: `${pk}-${sk}`,
id: generateId(pk, sk),
Explanation: Use generateId() for consistent ID generation across the framework.
AP009: Missing DTO Validation Decorators
Severity: Warning
Pattern:
export class CreateOrderDto {
code: string;
name: string;
}
export class CreateOrderDto {
@IsString()
@IsNotEmpty()
code: string;
@IsString()
@IsNotEmpty()
name: string;
}
Explanation: Always use class-validator decorators for input validation.
AP010: Deprecated Method Usage
Severity: Warning
Pattern:
await this.commandService.publish(command, options);
await this.commandService.publishPartialUpdate(command, options);
await this.commandService.publishAsync(command, options);
await this.commandService.publishPartialUpdateAsync(command, options);
Explanation: The non-Async methods are deprecated. Use publishAsync and publishPartialUpdateAsync.
AP011: Missing getCommandSource for Tracing
Severity: Warning
Pattern:
await this.commandService.publishAsync(command, {
invokeContext,
});
const commandSource = getCommandSource(
basename(__dirname),
this.constructor.name,
'create',
);
await this.commandService.publishAsync(command, {
source: commandSource,
invokeContext,
});
Explanation: Always include source in publish options for debugging and audit trails.
AP012: Direct DynamoDB Access Instead of DataService
Severity: Warning
Pattern:
import { DynamoDBClient, GetItemCommand } from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({});
const result = await client.send(new GetItemCommand({
TableName: 'my-table',
Key: { pk: { S: 'ORDER#tenant' }, sk: { S: 'ORDER#123' } },
}));
const result = await this.dataService.getItem({ pk, sk });
Explanation: Use DataService for read operations. It provides caching, consistent interfaces, and proper error handling.
AP013: Missing Type Declaration in DataSyncHandler
Severity: Error
Pattern:
@DataSyncHandler({})
export class OrderDataSyncRdsHandler implements IDataSyncHandler {
}
@DataSyncHandler({ type: 'ORDER' })
export class OrderDataSyncRdsHandler implements IDataSyncHandler {
}
Explanation: The type property in @DataSyncHandler decorator must match your entity's type field to route events correctly.
AP014: Not Using DetailKey Type
Severity: Info
Pattern:
async findOne(pk: string, sk: string) {
return this.dataService.getItem({ pk, sk });
}
import { DetailKey } from '@mbc-cqrs-serverless/core';
async findOne(key: DetailKey) {
return this.dataService.getItem(key);
}
Explanation: Use the DetailKey type for consistency and type safety across the application.
AP015: Hardcoded Table Names
Severity: Warning
Pattern:
CommandModule.register({
tableName: 'production-orders',
dataSyncHandlers: [OrderDataSyncRdsHandler],
}),
CommandModule.register({
tableName: process.env.ORDER_TABLE_NAME || 'orders',
dataSyncHandlers: [OrderDataSyncRdsHandler],
}),
Explanation: Table names should come from environment variables to support multiple environments (dev, staging, production).
AP016: Missing Error Logging
Severity: Warning
Pattern:
try {
await this.commandService.publishAsync(command, options);
} catch (error) {
throw new InternalServerErrorException();
}
private readonly logger = new Logger(OrderService.name);
try {
await this.commandService.publishAsync(command, options);
} catch (error) {
this.logger.error(`Failed to create order: ${error.message}`, error.stack);
if (error.name === 'ConditionalCheckFailedException') {
throw new ConflictException('Data was modified by another user');
}
throw error;
}
Explanation: Always log errors with context before handling them for debugging purposes.
AP017: Incorrect Attribute Merging
Severity: Error
Pattern:
await this.commandService.publishPartialUpdateAsync({
pk: key.pk,
sk: key.sk,
version: existingItem.version,
attributes: dto.attributes,
}, options);
await this.commandService.publishPartialUpdateAsync({
pk: key.pk,
sk: key.sk,
version: existingItem.version,
attributes: { ...existingItem.attributes, ...dto.attributes },
}, options);
Explanation: When updating, merge new attributes with existing ones to avoid data loss.
AP018: Missing Swagger Documentation
Severity: Info
Pattern:
@Controller('orders')
export class OrderController {
@Post()
async create(@Body() dto: CreateOrderDto) {}
}
@ApiTags('orders')
@Controller('orders')
export class OrderController {
@Post()
@ApiOperation({ summary: 'Create a new order' })
@ApiResponse({ status: 201, description: 'Order created successfully' })
@ApiResponse({ status: 409, description: 'Order already exists' })
async create(@Body() dto: CreateOrderDto) {}
}
Explanation: Add Swagger decorators for API documentation and better developer experience.
AP019: Not Handling Pagination Correctly
Severity: Warning
Pattern:
async search(dto: SearchOrderDto, invokeContext: IInvoke) {
const { tenantCode } = getUserContext(invokeContext);
return this.dataService.listByPk({
pk: `ORDER#${tenantCode}`,
});
}
async search(dto: SearchOrderDto, invokeContext: IInvoke) {
const { tenantCode } = getUserContext(invokeContext);
return this.dataService.listByPk({
pk: `ORDER#${tenantCode}`,
limit: dto.limit || 20,
cursor: dto.cursor,
});
}
Explanation: Always implement pagination to avoid returning large datasets and causing performance issues.
AP020: Circular Module Dependencies
Severity: Error
Pattern:
@Module({
imports: [ProductModule],
})
export class OrderModule {}
@Module({
imports: [OrderModule],
})
export class ProductModule {}
@Module({
imports: [forwardRef(() => ProductModule)],
})
export class OrderModule {}
@Module({
imports: [SharedModule],
})
export class OrderModule {}
Explanation: Avoid circular dependencies. Use forwardRef() as a last resort, or better, restructure your modules.
Review Checklist
When reviewing MBC CQRS Serverless code, check:
Module Structure
Service Layer
DTOs
Controller
Error Handling
Data Sync Handler
Output Format
When reviewing, provide output in this format:
## Code Review: [File Name]
### Issues Found
#### [AP00X] Issue Title
- **Severity:** Error/Warning/Info
- **Location:** Line XX
- **Current Code:**
```typescript
// problematic code
- Suggested Fix:
- Explanation: Why this is an issue
Summary
| Severity | Count |
|---|
| Error | X |
| Warning | X |
| Info | X |
Recommendations
- Priority fixes...
- Suggested improvements...