Skip to main content

Orchestration & Separate Runners

The engine provides separate, modular runners for different workflow types (workflow, feature, solution), enabling orchestration with shared context. All runners load and execute WSL files from disk.

Architecture​

Runner Types​

The engine includes three built-in runner types, each designed for a specific level of orchestration:

1. WorkflowRunner​

  • Handles execution of workflow type flows
  • Loads workflow_name.wsl files from disk
  • Executes individual workflow steps
  • Can run standalone or with shared context

2. FeatureRunner​

  • Handles execution of feature type flows
  • Loads feature_name.wsl files from disk
  • Orchestrates multiple workflows in a chain
  • All workflows in a feature share the same WorkerSessionContext
  • Provides workflow chaining capabilities

3. SolutionRunner​

  • Handles execution of solution type flows
  • Loads solution_name.wsl files from disk
  • Orchestrates both features and workflows
  • All features and workflows in a solution share the same WorkerSessionContext
  • Provides feature and workflow chaining capabilities

Shared Context​

All orchestrated components share the same WorkerSessionContext, enabling seamless data flow between workflows, features, and solutions.

// Values set in one workflow
workflowContext["shared_value"] = "data"

// Are accessible in subsequent workflows
value := workflowContext["shared_value"] // "data"

Basic Orchestration​

Feature Orchestrating Workflows​

A feature can orchestrate multiple workflows that run sequentially:

// feature_data_processing.wsl
module features

feature data_processing {
start: Step1

state Step1 {
action workflow process_step1
on success -> Step2
}

state Step2 {
action workflow process_step2
on success -> Step3
}

state Step3 {
action workflow finalize
on success -> Done
}

state Done {
end ok
}
}

Solution Orchestrating Features and Workflows​

A solution can orchestrate both features and workflows:

// solution_app.wsl
module solutions

solution complete_app {
start: RunFeature

state RunFeature {
action feature data_processing
on success -> RunWorkflow
}

state RunWorkflow {
action workflow reporting
on success -> Complete
}

state Complete {
action response/ResponseValue(message: "Solution complete", statusCode: 200)
end ok
}
}

Advanced Orchestration Patterns​

Sequential Workflow Chain​

Execute workflows one after another, sharing context:

feature user_onboarding {
start: CreateAccount

state CreateAccount {
action workflow create_user
on success -> SendWelcome
}

state SendWelcome {
action workflow send_welcome_email
on success -> SetupProfile
}

state SetupProfile {
action workflow initialize_profile
on success -> Complete
}

state Complete {
end ok
}
}

Mixed Orchestration​

Combine features and workflows in a solution:

solution ecommerce_platform {
start: SetupUsers

state SetupUsers {
action feature user_management
on success -> SetupCatalog
}

state SetupCatalog {
action workflow product_catalog_init
on success -> SetupPayments
}

state SetupPayments {
action feature payment_processing
on success -> StartCheckout
}

state StartCheckout {
action workflow checkout_flow
on success -> SystemReady
}

state SystemReady {
end ok
}
}

Conditional Orchestration​

Use conditional transitions with orchestration:

feature data_pipeline {
start: ValidateInput

state ValidateInput {
action workflow input_validation
on success -> CheckDataSize
}

state CheckDataSize {
action utils/check.DataSize() as SizeCheck
on success -> RouteBySize(SizeCheck)
}

state RouteBySize(SizeCheck) {
// If large dataset, use parallel processing
action workflow parallel_processor
on success -> Complete
on error -> UseSingleProcessor
}

state UseSingleProcessor {
action workflow single_processor
on success -> Complete
}

state Complete {
end ok
}
}

Context Sharing Example​

Here's how data flows through orchestrated workflows:

// workflow1.wsl - Sets shared data
workflow extract_data {
start: Fetch

state Fetch {
action data/fetch.FromAPI() as ApiData
on success -> StoreInContext(ApiData)
}

state StoreInContext(ApiData) {
action context/set.Value(key: "extracted_data", value: $ApiData)
end ok
}
}

// workflow2.wsl - Uses shared data
workflow transform_data {
start: LoadFromContext

state LoadFromContext {
action context/get.Value(key: "extracted_data") as ExtractedData
on success -> Transform(ExtractedData)
}

state Transform(ExtractedData) {
action transform/apply.Transformation(data: $ExtractedData)
on success -> SaveResult
}

state SaveResult {
end ok
}
}

// feature.wsl - Orchestrates both workflows
feature etl_pipeline {
start: Extract

state Extract {
action workflow extract_data
on success -> Transform
}

state Transform {
action workflow transform_data
on success -> Complete
}

state Complete {
end ok
}
}

Runner Factory​

The RunnerFactory automatically selects the appropriate runner based on flow type:

factory := workflow.NewRunnerFactory(config, app)
runner, err := factory.CreateRunner(flowType) // "workflow", "feature", or "solution"
if err != nil {
log.Fatal(err)
}

result := runner.Run(ctx, options)

Custom Runners​

Register custom runners for new workflow types:

// Register a custom runner
workflow.RegisterCustomRunner("pipeline", func(config domain.WorkflowConfigItem, app pkg.Application) workflow.Runner {
return &PipelineRunner{
config: config,
app: app,
}
})

// Use the custom runner
factory := workflow.NewRunnerFactory(config, app)
runner, err := factory.CreateRunner("pipeline")

Custom Runner Implementation​

type PipelineRunner struct {
config domain.WorkflowConfigItem
app pkg.Application
}

func (r *PipelineRunner) Run(ctx context.Context, options *domain.Options) map[string]*workflow.WorkerResponse {
// Load WSL file
wslContent, err := r.loadWSLFile(options.WorkflowName)
if err != nil {
return errorResponse(err)
}

// Parse and execute
result := r.executePipeline(ctx, wslContent, options)
return result
}

func (r *PipelineRunner) loadWSLFile(name string) (string, error) {
path := filepath.Join("runtime/workflows", name, name+".wsl")
content, err := os.ReadFile(path)
return string(content), err
}

Complete Example: E-commerce System​

Workflow Layer​

// workflow_user_auth.wsl
workflow user_authentication {
start: ValidateCredentials

state ValidateCredentials {
action auth/validate.Check()
on success -> GenerateToken
on error -> AuthFailed
}

state GenerateToken {
action auth/token.Create()
end ok
}

state AuthFailed {
end error
}
}

Feature Layer​

// feature_user_mgmt.wsl
feature user_management {
start: Authenticate

state Authenticate {
action workflow user_authentication
on success -> LoadProfile
on error -> HandleAuthError
}

state LoadProfile {
action workflow user_profile_load
on success -> Ready
}

state Ready {
end ok
}

state HandleAuthError {
end error
}
}

Solution Layer​

// solution_ecommerce.wsl
solution ecommerce_platform {
start: InitializeUsers

state InitializeUsers {
action feature user_management
on success -> InitializeProducts
}

state InitializeProducts {
action feature product_catalog
on success -> InitializePayments
}

state InitializePayments {
action feature payment_processing
on success -> SystemReady
}

state SystemReady {
action response/ResponseValue(message: "Platform ready", statusCode: 200)
end ok
}
}

Benefits​

1. Separation of Concerns​

Each runner handles its specific type independently, making code more maintainable.

2. Modularity​

Runners can be composed, reused, and extended for different use cases.

3. Shared Context​

Seamless data flow between orchestrated components without explicit parameter passing.

4. WSL File Execution​

Clean separation between workflow definition (WSL files) and execution (runners).

5. Extensibility​

New workflow types can be added via the Runner Registry without modifying core code.

6. Type Safety​

Proper validation ensures each workflow type is handled correctly.

Best Practices​

1. Use Appropriate Levels​

  • Workflows - For single-purpose, focused tasks
  • Features - For related workflows that work together
  • Solutions - For complete systems combining multiple features

2. Keep Workflows Small​

Each workflow should do one thing well:

// Good - focused workflow
workflow validate_email {
start: CheckFormat
state CheckFormat { ... }
state VerifyDomain { ... }
}

// Avoid - too much in one workflow
workflow handle_everything {
start: DoEverything
// 50+ states...
}

3. Name Files Clearly​

Use descriptive names that match the workflow name:

runtime/workflows/
user_authentication/
user_authentication.wsl
payment_processing/
payment_processing.wsl

4. Document Context Data​

Document what data each workflow expects and provides in shared context:

// Expects: context["user_id"]
// Provides: context["user_profile"]
workflow load_user_profile {
start: FetchProfile
// ...
}

5. Handle Errors Gracefully​

Include error handling at each orchestration level:

feature data_import {
start: Import

state Import {
action workflow import_data
on success -> Validate
on error -> HandleImportError
}

state HandleImportError {
action workflow cleanup_partial_import
end error
}

state Validate {
action workflow validate_imported_data
on success -> Complete
on error -> HandleValidationError
}

state HandleValidationError {
action workflow rollback_import
end error
}

state Complete {
end ok
}
}