Skip to content

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 successfully
  • StepResult::failed(string $error) - Step failed
  • StepResult::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 StepType implementations
  • ActionRegistry - Maps action handler names to classes
  • ConditionalRegistry - Maps conditional handler names to Conditional implementations
  • EventHandlerRegistry - Maps event names to WorkflowEvent DTOs and scopes actions/conditionals

Database Schema

workflows

Stores workflow definitions as JSON. Each row represents a specific version of a workflow.

ColumnTypeDescription
idbigintPrimary key
namestringWorkflow name
definitionjsonFull workflow definition
descriptionstringOptional description
event_namestringEvent name this workflow responds to
versionintegerVersion number (default: 1)
is_activebooleanWhether this version accepts new instances
created_byintegerOptional user ID

Compound unique constraint on (name, version).

workflow_instances

Runtime instances of workflows.

ColumnTypeDescription
idbigintPrimary key
uuidstringUnique identifier for event sourcing aggregate
workflow_idbigintFK to workflows (pinned version)
current_stepstringCurrent step name (null when completed)
statusstringpending, in_progress, completed, failed
workflow_eventtextSerialized WorkflowEvent DTO
statejsonMutable runtime variables (event data + step outputs)
error_messagetextError message if failed
created_byintegerOptional user ID
completed_attimestampWhen execution finished

step_executions

Tracks each step execution attempt.

ColumnTypeDescription
idbigintPrimary key
workflow_instance_idbigintFK to instances
step_namestringStep name from definition
statusstringpending, in_progress, completed, failed
inputjsonInput data at execution time
outputjsonOutput data from handler
errortextError message if failed
attemptsintegerNumber of attempts (for retries)
started_attimestampWhen execution started
completed_attimestampWhen execution finished

workflow_schedules

CRON-based workflow triggers.

ColumnTypeDescription
idbigintPrimary key
namestringSchedule name
event_namestringEvent name to trigger
workflow_eventtextSerialized WorkflowEvent DTO
cronstringCRON expression
is_enabledbooleanWhether this schedule is active
last_run_attimestampLast 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