Adding Handlers
The engine's execution logic is organized as event handlers. Each handler is responsible for one state transition or one type of job result processing. To extend the engine, you typically add a new handler.
The Handler Pattern
Every handler extends the EventHandler base class and implements:
abstract class EventHandler<TEvent extends EngineEvent> {
abstract handlesEventType(): new (...args: any[]) => TEvent
abstract eventConditions(): EventCondition[]
protected abstract executeInTransaction(
event: TEvent,
resolvedEntities: WorkflowEngineResolvedEntities,
em: EntityManager,
epoch: WorkflowEngineEpoch,
): Promise<EventHandlingVoidResultWithRetry>
}
Key Methods
handlesEventType()
: Returns the event class this handler processes. Currently two types: WorkflowExecutionUpdatedEvent and JobResultUpdatedEvent.
eventConditions()
: Returns an array of conditions that must all be true for this handler to be selected. Conditions can check workflow execution status, job status, and job result status.
executeInTransaction()
: The handler's logic, executed within a database transaction. Any entities modified here are flushed atomically.
Registering a Handler
Handlers are registered in CoreEngineService.initEngine():
private initEngine() {
// Workflow execution handlers
this.handlerRegistry.register(
WorkflowExecutionUpdatedEvent,
new NewWorkflowExecutionHandler(this.orm, this.cmsAccess),
)
// ... more handlers
// Job result handlers
this.handlerRegistry.register(
JobResultUpdatedEvent,
new JobResultRunningResultHandler(this.orm, this.cmsAccess),
)
}
The registry selects the handler whose event conditions match the incoming event. If multiple handlers match, the first registered one wins -- so order matters.
Example: Adding a New State Handler
To add handling for a new workflow state (e.g., a custom validation step):
- Create the handler class:
export class CustomValidationHandler extends EventHandler<WorkflowExecutionUpdatedEvent> {
handlesEventType() {
return WorkflowExecutionUpdatedEvent
}
eventConditions(): EventCondition[] {
return [
{
workflowExecutionStatus: WorkflowExecutionStatus.VALID,
// Additional conditions as needed
},
]
}
async executeInTransaction(event, resolvedEntities, em, epoch) {
const execution = await em.findOneOrFail(WorkflowExecution, event.entityId)
// Custom validation logic...
execution.status = WorkflowExecutionStatus.READY
return { retry: false }
}
}
- Register in
initEngine():
this.handlerRegistry.register(
WorkflowExecutionUpdatedEvent,
new CustomValidationHandler(this.em, ...),
)
- Write tests using the Playbook pattern (see E2E Playbooks).
Event Flow Recap
DB flush → Subscriber → EventAggregator → CoreEngine → HandlerRegistry → Handler
↓
DB changes
↓
DB flush (→ new events)
Each handler modifies entities and flushes. The flush may trigger new events (e.g., changing a workflow's status triggers the next handler), creating a chain of handlers that process the entire lifecycle.