Workflow Module
The workflow module provides state machine capabilities for your NestJS applications.
Installation
import { WorkflowModule } from 'nestjs-serverless-workflow/core';
Core Concepts
States
States represent the different stages your entity can be in during its lifecycle.
export enum OrderStatus {
Draft = 'draft',
Pending = 'pending',
Processing = 'processing',
Completed = 'completed',
Failed = 'failed',
}
Transitions
Transitions define how entities move from one state to another.
{
from: [OrderStatus.Pending],
to: OrderStatus.Processing,
event: 'order.submit',
conditions: [
(entity: Order, payload: any) => entity.items.length > 0,
(entity: Order, payload: any) => entity.totalAmount > 0,
],
}
Events
Events trigger state transitions. Define event handlers using the @OnEvent decorator:
@OnEvent('order.submit')
async onSubmit(@Entity() entity: Order, @Payload() data: SubmitOrderDto) {
// Validate and process the order
entity.submittedAt = new Date();
return entity;
}
Workflow Configuration
Basic Configuration
@Workflow({
name: 'OrderWorkflow',
states: {
finals: [OrderStatus.Completed, OrderStatus.Failed],
idles: [OrderStatus.Draft, OrderStatus.Pending],
failed: OrderStatus.Failed,
},
transitions: [
// Define your transitions here
],
entityService: 'entity.order',
brokerPublisher: 'broker.order',
})
export class OrderWorkflow {
// Event handlers
}
State Types
- finals: Terminal states where the workflow ends
- idles: States where the workflow waits for external events
- failed: The failure state to transition to on errors
Conditional Transitions
Add conditions to control when transitions can occur:
{
from: [OrderStatus.Pending],
to: OrderStatus.Processing,
event: 'order.submit',
conditions: [
(entity: Order, payload: any) => entity.price > 10,
(entity: Order, payload: any) => entity.inventory.available,
],
}
All conditions must return true for the transition to proceed.
Decorators
@Workflow(config)
Marks a class as a workflow definition.
@OnEvent(eventName)
Defines an event handler for the specified event.
@Entity()
Injects the entity being processed into the handler.
@Payload()
Injects the event payload. Optionally, pass a DTO class for validation:
@OnEvent('order.submit')
async onSubmit(@Entity() entity: Order, @Payload(SubmitOrderDto) data: SubmitOrderDto) {
// data is validated and transformed
}
@WithRetry(config)
Adds retry logic to event handlers:
@OnEvent('order.payment')
@WithRetry({ maxAttempts: 3, backoff: 'exponential' })
async processPayment(@Entity() entity: Order) {
// Will retry up to 3 times with exponential backoff
}
@OnDefault
Defines a fallback handler for unhandled events:
@OnDefault
async fallback(entity: Order, event: string, payload?: any) {
console.warn(`Unhandled event: ${event}`);
return entity;
}
Entity Service Interface
Implement the IWorkflowEntity interface to integrate with your data layer:
export interface IWorkflowEntity<T = any, State = string | number> {
create(): Promise<T>;
load(urn: string | number): Promise<T | null>;
update(entity: T, status: State): Promise<T>;
status(entity: T): State;
urn(entity: T): string | number;
}
Module Registration
WorkflowModule.register({
imports: [DatabaseModule],
entities: [
{ provide: 'entity.order', useClass: OrderEntityService },
],
workflows: [OrderWorkflow, UserWorkflow],
brokers: [
{ provide: 'broker.order', useClass: SqsEmitter },
],
})
Orchestrator Service
The OrchestratorService handles workflow execution:
import { OrchestratorService } from 'nestjs-serverless-workflow/core';
@Injectable()
export class MyService {
constructor(private orchestrator: OrchestratorService) {}
async processEvent(event: IWorkflowEvent) {
await this.orchestrator.transit(event);
}
}
Error Handling
Unretriable Errors
Use UnretriableException for errors that should not be retried:
import { UnretriableException } from 'nestjs-serverless-workflow/exception';
@OnEvent('order.validate')
async validate(@Entity() entity: Order) {
if (entity.totalAmount < 0) {
throw new UnretriableException('Order amount cannot be negative');
}
}
Retry Configuration
Configure retry behavior at the handler level:
@OnEvent('order.payment')
@WithRetry({
maxAttempts: 5,
backoff: 'exponential',
initialDelay: 1000,
maxDelay: 30000,
})
async processPayment(@Entity() entity: Order) {
// This will retry with exponential backoff
}
Best Practices
- Keep workflows stateless: Store state in your entities, not in workflow classes
- Use idempotent handlers: Events may be processed multiple times
- Handle errors gracefully: Use try-catch blocks and proper error types
- Validate payloads: Use DTOs with class-validator for payload validation
- Test thoroughly: Use the testing utilities to test your workflows in isolation
Examples
See the examples directory for complete working examples.