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
workflowtype flows - Loads
workflow_name.wslfiles from disk - Executes individual workflow steps
- Can run standalone or with shared context
2. FeatureRunner
- Handles execution of
featuretype flows - Loads
feature_name.wslfiles 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
solutiontype flows - Loads
solution_name.wslfiles 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
}
}
Related Features
- Custom Workflow Types - Using project, application, feature, solution types
- Conditional Flows - Conditional transitions and flow control
- WSL Overview - Introduction to WSL syntax
- Examples - More workflow examples