Architecture
System Overview
Workflowable is built as a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────────┐
│ Facade Layer │
│ Workflowable::dispatch() / dispatchSync() │
├─────────────────────────────────────────────────┤
│ Engine Layer │
│ WorkflowEngine │ StepExecutor │ Transition │
├─────────────────────────────────────────────────┤
│ Registry Layer │
│ StepType │ Action │ Conditional │ EventHandler │
├─────────────────────────────────────────────────┤
│ Model Layer │
│ Workflow │ Instance │ StepExecution │ Schedule │
├─────────────────────────────────────────────────┤
│ Event Sourcing Layer │
│ WorkflowInstanceAggregate (Spatie) │
└─────────────────────────────────────────────────┘Core Components
WorkflowEngine
The main orchestrator responsible for:
- Creating workflow instances from events
- Executing steps in sequence
- Handling transitions between steps
- Managing the execution lifecycle
StepExecutor
Resolves step types from the StepTypeRegistry and delegates execution. Each step type implements the StepType interface.
ExecutionContext
A runtime state holder that wraps a WorkflowInstance and provides access to:
- Workflow variables (event data + step outputs merged)
- Current step information
- Instance metadata
StepResult
A value object returned by step handlers with:
StepResult::success(mixed $output = null, ?string $nextStep = null, ?string $condition = null)- Step completed successfullyStepResult::failed(string $error)- Step failedStepResult::waiting(mixed $output = null, ?string $nextStep = null)- Step is waiting (e.g. for a custom async step type)
Registries
Four registries manage extensibility:
- StepTypeRegistry - Maps step type names to
StepTypeimplementations - ActionRegistry - Maps action handler names to classes
- ConditionalRegistry - Maps conditional handler names to
Conditionalimplementations - EventHandlerRegistry - Maps event names to
WorkflowEventDTOs and scopes actions/conditionals
Database Schema
workflows
Stores workflow definitions as JSON. Each row represents a specific version of a workflow.
| Column | Type | Description |
|---|---|---|
| id | bigint | Primary key |
| name | string | Workflow name |
| definition | json | Full workflow definition |
| description | string | Optional description |
| event_name | string | Event name this workflow responds to |
| version | integer | Version number (default: 1) |
| is_active | boolean | Whether this version accepts new instances |
| created_by | integer | Optional user ID |
Compound unique constraint on (name, version).
workflow_instances
Runtime instances of workflows.
| Column | Type | Description |
|---|---|---|
| id | bigint | Primary key |
| uuid | string | Unique identifier for event sourcing aggregate |
| workflow_id | bigint | FK to workflows (pinned version) |
| current_step | string | Current step name (null when completed) |
| status | string | pending, in_progress, completed, failed |
| workflow_event | text | Serialized WorkflowEvent DTO |
| state | json | Mutable runtime variables (event data + step outputs) |
| error_message | text | Error message if failed |
| created_by | integer | Optional user ID |
| completed_at | timestamp | When execution finished |
step_executions
Tracks each step execution attempt.
| Column | Type | Description |
|---|---|---|
| id | bigint | Primary key |
| workflow_instance_id | bigint | FK to instances |
| step_name | string | Step name from definition |
| status | string | pending, in_progress, completed, failed |
| input | json | Input data at execution time |
| output | json | Output data from handler |
| error | text | Error message if failed |
| attempts | integer | Number of attempts (for retries) |
| started_at | timestamp | When execution started |
| completed_at | timestamp | When execution finished |
workflow_schedules
CRON-based workflow triggers.
| Column | Type | Description |
|---|---|---|
| id | bigint | Primary key |
| name | string | Schedule name |
| event_name | string | Event name to trigger |
| workflow_event | text | Serialized WorkflowEvent DTO |
| cron | string | CRON expression |
| is_enabled | boolean | Whether this schedule is active |
| last_run_at | timestamp | Last execution time (for duplicate prevention) |
Execution Flow
Phase 1: INITIALIZATION
1. Receive WorkflowEvent DTO (typed, encrypted fields)
2. Look up all active workflow definitions bound to the event name
3. Validate definition structure
4. Create WorkflowInstance (status: pending, current_step: initial_step)
5. Record WorkflowWasCreated domain event
6. Dispatch WorkflowStarted event
Phase 2: STEP EXECUTION (repeats)
1. Resolve step type from StepTypeRegistry
2. Create StepExecution record (status: pending)
3. Dispatch StepStarted event
4. Execute step handler:
- action: Call registered action or class handler
- conditional: Resolve registered conditional handler, evaluate, return true/false
- custom: Delegate to registered StepType implementation
5. Record result, dispatch StepCompleted or StepFailed
Phase 3: TRANSITION
1. Look up next step from transitions definition
2. For conditionals: route based on 'true'/'false' result
3. Update current_step, record transition
4. Loop back to Phase 2
Phase 4: COMPLETION
- 'completed' terminal: mark instance completed
- 'failed' terminal: mark instance failed
- No valid transition: mark failed