Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Prerequisites:
[Coinbase Ruby SDK](https://github.com/coinbase/temporal-ruby).
* [context_propagation](context_propagation) - Use interceptors to propagate thread/fiber local data from clients
through workflows/activities.
* [dsl](dsl) - Execute workflows defined in a YAML-based domain-specific language.
* [message_passing_simple](message_passing_simple) - Simple workflow that accepts signals, queries, and updates.
* [sorbet_generic](sorbet_generic) - Proof of concept of how to do _advanced_ Sorbet typing with the SDK.

Expand Down
69 changes: 69 additions & 0 deletions dsl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# DSL Sample

This sample demonstrates how to create a workflow that can interpret and execute operations defined in a YAML-based Domain Specific Language (DSL). This approach allows you to define workflow steps declaratively in a YAML file, which can be modified without changing the underlying code.

## Overview

The sample shows how to:

1. Define a DSL schema for workflow operations
2. Parse YAML files into Ruby objects
3. Create a workflow that can interpret and execute these operations
4. Execute activities based on the DSL definition
5. Support sequential and parallel execution patterns

## Prerequisites

See the [main README](../README.md) for prerequisites and setup instructions.

## Running the Sample

1. Start the worker in one terminal:

```
ruby worker.rb
```

2. Execute a workflow using one of the example YAML definitions:

```
ruby starter.rb workflow1.yaml
```

This will run the simple sequential workflow defined in `workflow1.yaml`.

3. Try the more complex workflow with parallel execution:

```
ruby starter.rb workflow2.yaml
```

## Understanding the Sample

### DSL Model

The DSL allows defining:

- Variables to be used across the workflow
- Activities with inputs and outputs
- Sequential execution of steps
- Parallel execution of branches

### Workflow Structure

- `my_activities.rb` - Example activities that can be executed by the workflow
- `dsl_models.rb` - Classes representing the DSL schema
- `dsl.rb` - Workflow implementation that interprets and executes the DSL
- `worker.rb` - Worker process that hosts the activities and workflow
- `starter.rb` - Client that reads a YAML file and executes the workflow
- `workflow1.yaml` - Simple sequential workflow example
- `workflow2.yaml` - More complex workflow with parallel execution

### Extending the Sample

You can extend this sample by:

1. Adding more activity types
2. Extending the DSL with new statement types (e.g., conditionals, loops)
3. Adding error handling and retry mechanisms
4. Creating validation for the DSL input
42 changes: 42 additions & 0 deletions dsl/activities.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require 'temporalio/activity'

module Dsl
module Activities
class Activity1 < Temporalio::Activity::Definition
def execute(arg)
Temporalio::Activity::Context.current.logger.info("Executing activity1 with arg: #{arg}")
"[result from activity1: #{arg}]"
end
end

class Activity2 < Temporalio::Activity::Definition
def execute(arg)
Temporalio::Activity::Context.current.logger.info("Executing activity2 with arg: #{arg}")
"[result from activity2: #{arg}]"
end
end

class Activity3 < Temporalio::Activity::Definition
def execute(arg1, arg2)
Temporalio::Activity::Context.current.logger.info("Executing activity3 with args: #{arg1} and #{arg2}")
"[result from activity3: #{arg1} #{arg2}]"
end
end

class Activity4 < Temporalio::Activity::Definition
def execute(arg)
Temporalio::Activity::Context.current.logger.info("Executing activity4 with arg: #{arg}")
"[result from activity4: #{arg}]"
end
end

class Activity5 < Temporalio::Activity::Definition
def execute(arg1, arg2)
Temporalio::Activity::Context.current.logger.info("Executing activity5 with args: #{arg1} and #{arg2}")
"[result from activity5: #{arg1} #{arg2}]"
end
end
end
end
152 changes: 152 additions & 0 deletions dsl/dsl_models.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# frozen_string_literal: true

module Dsl
module Models
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module name and file name should match

# Base class for DSL input with root statement and variables
class DslInput
attr_reader :root, :variables

def initialize(root, variables = {})
@root = root
@variables = variables
end

def to_h
{
'root' => @root.to_h,
'variables' => @variables
}
end
end

# Activity invocation model
class ActivityInvocation
attr_reader :name, :arguments, :result

def initialize(name, arguments = [], result = nil)
@name = name
@arguments = arguments
@result = result
end

def to_h
{
'name' => @name,
'arguments' => @arguments,
'result' => @result
}
end
end

# Activity statement model
class ActivityStatement
attr_reader :activity

def initialize(activity)
@activity = activity
end

def to_h
{
'activity' => @activity.to_h
}
end
end

# Sequence model containing list of statements
class Sequence
attr_reader :elements

def initialize(elements)
@elements = elements
end

def to_h
{
'elements' => @elements.map(&:to_h)
}
end
end

# Sequence statement model
class SequenceStatement
attr_reader :sequence

def initialize(sequence)
@sequence = sequence
end

def to_h
{
'sequence' => @sequence.to_h
}
end
end

# Parallel model containing list of branches
class Parallel
attr_reader :branches

def initialize(branches)
@branches = branches
end

def to_h
{
'branches' => @branches.map(&:to_h)
}
end
end

# Parallel statement model
class ParallelStatement
attr_reader :parallel

def initialize(parallel)
@parallel = parallel
end

def to_h
{
'parallel' => @parallel.to_h
}
end
end

# Parse YAML to DSL models
class Parser
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this class used?

def self.parse_yaml(yaml_content)
require 'yaml'
data = YAML.safe_load(yaml_content)

variables = data['variables'] || {}
root = parse_statement(data['root'])

DslInput.new(root, variables)
end

def self.parse_statement(data)
case data.keys.first
when 'activity'
activity_data = data['activity']
activity = ActivityInvocation.new(
activity_data['name'],
activity_data['arguments'] || [],
activity_data['result']
)
ActivityStatement.new(activity)
when 'sequence'
sequence_data = data['sequence']
elements = sequence_data['elements'].map { |elem| parse_statement(elem) }
SequenceStatement.new(Sequence.new(elements))
when 'parallel'
parallel_data = data['parallel']
branches = parallel_data['branches'].map { |branch| parse_statement(branch) }
ParallelStatement.new(Parallel.new(branches))
else
raise "Unknown statement type: #{data.keys.first}"
end
end
end
end
end
Loading