Orchestrating Workflows with Process Managers
Overview
Policies react to individual events. When a workflow spans multiple events over time — for example, a task that must be reviewed within a deadline — you need a process manager. A process manager tracks the state of a long-running process, responds to events along the way, and can enforce timeouts.
In this step, you will create a task review process that starts when a task is assigned and completes when the task is finished. If the review takes too long, a timeout handler runs.
How Process Managers Work
A process manager is defined by:
- A start event — the event that kicks off the process.
- A completion event — the event that marks the process as done.
- A timeout — an optional duration after which the process expires if not completed.
- Event handlers — functions that run when events relevant to the process are published.
Bounda tracks each process instance by aggregate ID. When the start event fires, a new process instance is created. When the completion event fires, the instance is marked as done and no further handlers run.
Create the Process Manager Directory
Process managers live inside the aggregate directory under processes/. Create the directory:
mkdir -p src/domain/task/processes/task-review
Define the Process Configuration
Create src/domain/task/processes/task-review/index.ts:
import type { ProcessManager } from "./+types/task-review";
export const config: ProcessManager.ConfigFunction = ({ events }) => ({
startedBy: [events.TaskAssigned],
completedBy: [events.TaskCompleted],
timeout: "7d",
});
The config function declares:
startedBy— the process begins when aTaskAssignedevent is published.completedBy— the process ends when aTaskCompletedevent is published.timeout— if 7 days pass without the completion event, the process times out.
The events object provided to the config function contains references to all events in the aggregate. This keeps the configuration type-safe.
Handle the Start Event
Create src/domain/task/processes/task-review/on-task-assigned.ts:
import type { ProcessManager } from "./+types/on-task-assigned";
export async function handler({ event }: ProcessManager.HandlerArgs) {
console.log(`Review process started for task ${event.aggregateId}`);
}
This handler runs when the process starts. In a real application, you might dispatch a command to create a review checklist, notify a reviewer, or set up tracking.
Handle the Timeout
Create src/domain/task/processes/task-review/on-task-review-timed-out.ts:
import type { ProcessManager } from "./+types/on-task-review-timed-out";
export async function handler({ event }: ProcessManager.HandlerArgs) {
console.log(`Review timed out for task ${event.aggregateId}`);
}
The timeout handler runs when the configured duration expires without the completion event firing. The naming convention is on-<process-name>-timed-out.ts.
Timeout handlers are useful for escalation: reassigning a task, sending a reminder, or marking the process as stale.
Regenerate Types
npx bounda generate
Try It Out
import { boot } from "@bounda-dev/core";
const app = await boot();
await app.commands.createTask({
aggregateId: "task-1",
title: "Implement login page",
});
await app.commands.assignTask({
aggregateId: "task-1",
assignee: "bob",
});
After the assignTask call, the review process starts. You will see:
Review process started for task task-1
If the task is completed within 7 days:
await app.commands.completeTask({ aggregateId: "task-1" });
The process is marked as done and the timeout is cancelled. If 7 days pass without completion, the timeout handler fires instead.
Process Manager Lifecycle
TaskAssigned --> process starts --> on-task-assigned handler runs
|
+--> TaskCompleted (within 7d) --> process completes
|
+--> 7 days elapse --> on-task-review-timed-out handler runs
A process manager instance is tied to a single aggregate ID. Multiple tasks can each have their own independent review process running concurrently.
When to Use Policies vs. Process Managers
| Scenario | Use |
|---|---|
| React to a single event with a side effect | Policy |
| Track a workflow that spans multiple events | Process manager |
| Enforce a deadline on a multi-step process | Process manager |
| Fire-and-forget notification | Policy |
Policies are stateless. Process managers maintain state across events and time.
What We Built
- Created a
task-reviewprocess manager that starts on task assignment and completes on task completion. - Added a 7-day timeout with a handler that runs when the deadline is missed.
- Learned the lifecycle of a process manager: start, handle events, complete or time out.
Process managers coordinate multi-step workflows. Next, you will learn how to inject external dependencies into commands using collaborators.
Next step: Injecting Dependencies with Collaborators