Handlers Not Discovered
Handlers Not Discovered
Section titled “Handlers Not Discovered”Observable Symptoms
Section titled “Observable Symptoms”- Service starts successfully but reports 0 handlers discovered
- Log message shows:
Handler discovery completed { commandHandlers: 0, queryHandlers: 0, eventHandlers: 0 } - API calls return 404 or “Handler not found” errors
- Contract operations fail with “No handler found for message type”
Quick Fix
Section titled “Quick Fix”# Check handler discovery logsdocker logs my-service 2>&1 | grep "Handler discovery"
# Verify handler files exist in correct locationsls -la src/commands/ls -la src/queries/ls -la src/subscriptions/
# Check for decorator and exportgrep -r "@CommandHandlerDecorator\|@QueryHandlerDecorator\|@EventSubscriptionHandlerDecorator" src/Common Causes (Ordered by Frequency)
Section titled “Common Causes (Ordered by Frequency)”1. Wrong Directory Location
Section titled “1. Wrong Directory Location”Frequency: Very Common (40% of cases)
Symptoms:
- Handlers exist but not in conventional directories
- Using
events/instead ofsubscriptions/ - Using custom directory names
Diagnostic Steps:
# Check where handler files are locatedfind src/ -name "*Handler.ts" -type f
# Should be in:# src/commands/# src/queries/# src/subscriptions/
# NOT in:# src/handlers/# src/events/# src/lib/Solution:
Move handlers to correct directories:
# Wrong locationssrc/handlers/CreateUserHandler.ts ❌src/events/UserCreatedHandler.ts ❌
# Correct locationssrc/commands/CreateUserHandler.ts ✓src/queries/GetUserHandler.ts ✓src/subscriptions/UserCreatedHandler.ts ✓Prevention:
- Use CLI to generate handlers:
npx @banyanai/platform-cli generate handler CreateUser - Follow project template structure
- Review Handler Discovery Pattern
2. Missing Decorator
Section titled “2. Missing Decorator”Frequency: Very Common (35% of cases)
Symptoms:
- Handler in correct directory but not discovered
- No TypeScript compilation errors
- Class exists and is exported
Diagnostic Steps:
// Check handler file for decorator// ❌ WRONG: No decoratorexport class CreateUserHandler extends CommandHandler<CreateUserCommand, CreateUserResult> { async handle(command: CreateUserCommand, user: AuthenticatedUser | null) { // ... }}
// ✓ CORRECT: Has decorator@CommandHandlerDecorator(CreateUserCommand)export class CreateUserHandler extends CommandHandler<CreateUserCommand, CreateUserResult> { async handle(command: CreateUserCommand, user: AuthenticatedUser | null) { // ... }}Solution:
Add appropriate decorator:
// Command handlersimport { CommandHandlerDecorator } from '@banyanai/platform-cqrs';import { CreateUserCommand } from '../contracts/commands.js';
@CommandHandlerDecorator(CreateUserCommand)export class CreateUserHandler extends CommandHandler<...> { }
// Query handlersimport { QueryHandlerDecorator } from '@banyanai/platform-cqrs';import { GetUserQuery } from '../contracts/queries.js';
@QueryHandlerDecorator(GetUserQuery)export class GetUserHandler extends QueryHandler<...> { }
// Event handlersimport { EventSubscriptionHandlerDecorator } from '@banyanai/platform-message-bus-client';import { UserCreatedEvent } from '../contracts/events.js';
@EventSubscriptionHandlerDecorator(UserCreatedEvent)export class UserCreatedHandler extends EventSubscriptionHandler<...> { }Prevention:
- Use code snippets/templates with decorators
- Enable TypeScript strict mode to catch missing metadata
- Use handler generator CLI commands
3. Wrong Decorator Argument (String Instead of Class)
Section titled “3. Wrong Decorator Argument (String Instead of Class)”Frequency: Common (15% of cases)
Symptoms:
- Handler has decorator but still not discovered
- No TypeScript errors
- Decorator appears correct visually
Diagnostic Steps:
// Check decorator argument type
// ❌ WRONG: String literal@CommandHandlerDecorator('CreateUserCommand')export class CreateUserHandler { }
// ❌ WRONG: Using .name property@CommandHandlerDecorator(CreateUserCommand.name)export class CreateUserHandler { }
// ✓ CORRECT: Class constructor referenceimport { CreateUserCommand } from '../contracts/commands.js'; // VALUE import@CommandHandlerDecorator(CreateUserCommand)export class CreateUserHandler { }Solution:
Use class constructor reference:
// Import contract as VALUE (not type-only)import { CreateUserCommand } from '../contracts/commands.js';
// Decorator needs runtime value@CommandHandlerDecorator(CreateUserCommand) // Pass the class itselfexport class CreateUserHandler extends CommandHandler<CreateUserCommand, CreateUserResult> { async handle(command: CreateUserCommand, user: AuthenticatedUser | null) { // ... }}Check imports:
// ❌ WRONG: Type-only import (no runtime value)import type { CreateUserCommand } from '../contracts/commands.js';
// ✓ CORRECT: Value importimport { CreateUserCommand } from '../contracts/commands.js';Prevention:
- Never use string literals in decorators
- Import contracts as values, not types
- Use const types for compile-time safety
4. Incorrect Filename
Section titled “4. Incorrect Filename”Frequency: Common (10% of cases)
Symptoms:
- Handler in correct directory with decorator
- Filename doesn’t match convention
Diagnostic Steps:
# Check filenamesls src/commands/
# ❌ WRONG filenames:CreateUser.ts # Missing "Handler"CreateUserCommand.ts # Wrong suffixcreate-user-handler.ts # Wrong case (lowercase)createUserHandler.ts # Wrong case (camelCase)
# ✓ CORRECT filenames:CreateUserHandler.ts # PascalCase with "Handler" suffixSolution:
Rename files to match convention:
# Must end with "Handler.ts"# Must be PascalCase
mv CreateUser.ts CreateUserHandler.tsmv create-user-handler.ts CreateUserHandler.tsConvention:
- Format:
{ActionName}Handler.ts - Case: PascalCase
- Suffix: Must end with
Handler.ts
Prevention:
- Use handler generator CLI
- Configure linter to enforce naming convention
- Document naming patterns in team guidelines
5. Missing Export
Section titled “5. Missing Export”Frequency: Occasional (5% of cases)
Symptoms:
- Handler file exists with decorator
- TypeScript compiles without errors
- Class not accessible to discovery scanner
Diagnostic Steps:
// Check if class is exported
// ❌ WRONG: Not exported@CommandHandlerDecorator(CreateUserCommand)class CreateUserHandler extends CommandHandler<...> { }
// ✓ CORRECT: Exported@CommandHandlerDecorator(CreateUserCommand)export class CreateUserHandler extends CommandHandler<...> { }Solution:
Add export keyword:
// Ensure class is exportedexport class CreateUserHandler extends CommandHandler<CreateUserCommand, CreateUserResult> { async handle(command: CreateUserCommand, user: AuthenticatedUser | null) { // ... }}Prevention:
- Always export handler classes
- Use ESLint rule to require exports
- Review build output to verify exports
6. TypeScript Configuration Issues
Section titled “6. TypeScript Configuration Issues”Frequency: Occasional (3% of cases)
Symptoms:
- Decorators stripped during compilation
- Build succeeds but decorators not present in JavaScript output
- Runtime reflection metadata missing
Diagnostic Steps:
Check tsconfig.json:
{ "compilerOptions": { "experimentalDecorators": true, // REQUIRED "emitDecoratorMetadata": true, // REQUIRED "target": "ES2022", "module": "Node16", "moduleResolution": "Node16" }}Verify compiled output:
# Build servicepnpm run build
# Check compiled JavaScript has decorator metadatacat dist/commands/CreateUserHandler.js
# Should see __decorate or similar decorator metadataSolution:
Update tsconfig.json:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "esModuleInterop": true, "skipLibCheck": true, "strict": true }}Rebuild service:
# Clean build artifactsrm -rf dist/
# Rebuildpnpm run build
# Restart servicedocker compose restart my-servicePrevention:
- Use provided
tsconfig.jsontemplate - Don’t override critical compiler options
- Run type checking in CI/CD
7. Decorator and Handler Type Mismatch
Section titled “7. Decorator and Handler Type Mismatch”Frequency: Rare (2% of cases)
Symptoms:
- Handler discovered but fails at runtime
- Type errors in logs
- Contract/handler mismatch errors
Diagnostic Steps:
// Check decorator matches base class
// ❌ WRONG: Query decorator with Command handler@QueryHandlerDecorator(CreateUserCommand)export class CreateUserHandler extends CommandHandler<...> { }
// ❌ WRONG: Command decorator with Query handler@CommandHandlerDecorator(GetUserQuery)export class GetUserHandler extends QueryHandler<...> { }
// ✓ CORRECT: Matching types@CommandHandlerDecorator(CreateUserCommand)export class CreateUserHandler extends CommandHandler<...> { }
@QueryHandlerDecorator(GetUserQuery)export class GetUserHandler extends QueryHandler<...> { }
@EventSubscriptionHandlerDecorator(UserCreatedEvent)export class UserCreatedHandler extends EventSubscriptionHandler<...> { }Solution:
Match decorator to handler type:
| Handler Type | Decorator | Base Class |
|---|---|---|
| Command | @CommandHandlerDecorator(...) | extends CommandHandler<...> |
| Query | @QueryHandlerDecorator(...) | extends QueryHandler<...> |
| Event | @EventSubscriptionHandlerDecorator(...) | extends EventSubscriptionHandler<...> |
Prevention:
- Use consistent naming (CommandHandler for commands, etc.)
- Code review for type consistency
- Unit tests to verify handler registration
Verification Steps
Section titled “Verification Steps”After fixing the issue, verify handlers are discovered:
1. Check Service Logs
Section titled “1. Check Service Logs”# View startup logsdocker logs my-service 2>&1 | grep -A 5 "Handler discovery"
# Should show:# Handler discovery completed {# commandHandlers: 3,# queryHandlers: 2,# eventHandlers: 1,# totalHandlers: 6# }2. Test Handler Execution
Section titled “2. Test Handler Execution”# Test command handlercurl -X POST http://localhost:3000/api/create-user \ -H "Content-Type: application/json" \ -H "X-Dev-User-Id: test-user" \ -H "X-Dev-Permissions: users:create" \ -d '{"email":"test@example.com","password":"password123"}'
# Should return 200 OK with result (not 404)3. Check Contract Broadcasting
Section titled “3. Check Contract Broadcasting”# Verify contracts are broadcast to service discoverycurl http://localhost:3001/api/services/my-service/contracts | jq
# Should show handlers in contract list4. Enable Debug Logging
Section titled “4. Enable Debug Logging”# Set environment variableLOG_LEVEL=debug docker compose up my-service
# Look for detailed discovery logs:# [DEBUG] Scanning directory: /app/service/dist/commands# [DEBUG] Found file: CreateUserHandler.js# [DEBUG] Loading handler: CreateUserHandler# [DEBUG] Handler registered: CreateUserHandler for CreateUserCommandAdvanced Debugging
Section titled “Advanced Debugging”Manual Discovery Test
Section titled “Manual Discovery Test”Create a test script to verify handler metadata:
import 'reflect-metadata';import { CreateUserHandler } from './commands/CreateUserHandler.js';
// Check decorator metadataconst metadata = Reflect.getMetadata('banyan:command-handler', CreateUserHandler);console.log('Handler metadata:', metadata);
// Should output: { contractClass: [Function: CreateUserCommand] }Check Build Output
Section titled “Check Build Output”# Verify handlers are in build outputfind dist/ -name "*Handler.js" -type f
# Check JavaScript has decorator metadatacat dist/commands/CreateUserHandler.js | grep -A 10 "__decorate"Verify Handler Registration
Section titled “Verify Handler Registration”// In service code, log handler registryimport { HandlerRegistry } from '@banyanai/platform-cqrs';
const registry = HandlerRegistry.getInstance();const handlers = registry.getAllHandlers();
console.log('Registered handlers:', { commands: handlers.commands.size, queries: handlers.queries.size, events: handlers.events.size});Directory Structure Reference
Section titled “Directory Structure Reference”Correct Structure
Section titled “Correct Structure”service/src/├── commands/│ ├── CreateUserHandler.ts│ ├── UpdateUserHandler.ts│ └── DeleteUserHandler.ts├── queries/│ ├── GetUserHandler.ts│ └── ListUsersHandler.ts├── subscriptions/│ ├── UserCreatedHandler.ts│ └── UserUpdatedHandler.ts├── contracts/│ ├── commands.ts│ ├── queries.ts│ └── events.ts└── index.tsSubdirectories Supported
Section titled “Subdirectories Supported”Handlers can be organized in subdirectories:
service/src/├── commands/│ ├── users/│ │ ├── CreateUserHandler.ts│ │ └── UpdateUserHandler.ts│ └── organizations/│ ├── CreateOrganizationHandler.ts│ └── UpdateOrganizationHandler.ts└── queries/ └── users/ └── GetUserHandler.tsAll handlers are discovered recursively.
Complete Handler Checklist
Section titled “Complete Handler Checklist”Before deploying, verify each handler meets ALL requirements:
- Handler file in correct directory (
commands/,queries/, orsubscriptions/) - Filename ends with
Handler.tsin PascalCase - Class has appropriate decorator (
@CommandHandlerDecorator,@QueryHandlerDecorator, or@EventSubscriptionHandlerDecorator) - Decorator argument is class constructor (not string)
- Contract imported as value (not type-only import)
- Handler extends appropriate base class
- Class is exported with
exportkeyword - TypeScript compiles without errors
-
experimentalDecorators: trueintsconfig.json -
emitDecoratorMetadata: trueintsconfig.json - Build output contains decorator metadata
Related Documentation
Section titled “Related Documentation”- Handler Development - How to write handlers
- Contracts - Contract definition
- Service Startup - BaseService.start() behavior
- Log Analysis - Reading service logs
- Error Catalog - HANDLER_NOT_FOUND error
Summary
Section titled “Summary”Handler discovery is automatic but requires strict adherence to conventions:
- Location:
commands/,queries/, orsubscriptions/directories - Naming:
{ActionName}Handler.tsin PascalCase - Decorator: Appropriate decorator with class reference (not string)
- Export: Class must be exported
- TypeScript: Decorators enabled in tsconfig
Follow these conventions and handlers will be discovered without any manual registration.