mirror of
https://github.com/temporalio/skill-temporal-developer.git
synced 2026-09-14 13:52:58 +08:00
d5c47df820
* Add all Ruby SDK reference files (11 files, ~2100 lines) Created complete Ruby reference documentation covering: - ruby.md: Overview, quick start, key concepts, file organization - patterns.md: Signals, queries, updates, child workflows, saga, cancellation, etc. - determinism.md: Illegal call tracing, safe alternatives table - determinism-protection.md: TracePoint, durable fiber scheduler, customization - versioning.md: Patching API, type versioning, worker versioning - testing.md: WorkflowEnvironment, mocking, replay, activity testing - error-handling.md: ApplicationError, retries, timeouts, workflow failure - data-handling.md: Data converter, ActiveModel, hints, search attributes - observability.md: Logging, metrics, best practices - gotchas.md: Common mistakes, illegal call tracing issues - advanced-features.md: Schedules, async completion, worker tuning, Rails Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix alignment issues in Ruby reference files Self-review fixes: - patterns.md: Remove non-existent `workflow_run` annotation; entry point is `def execute` (no annotation needed, unlike Python's @workflow.run) - patterns.md: Remove conflicting manual query methods that duplicated workflow_query_attr_reader - error-handling.md: Remove `await` keyword (doesn't exist in Ruby) - gotchas.md: Replace TS-style CancellationScope with Ruby's Temporalio::Cancellation token-based detached cancellation - data-handling.md: Replace homemade ActiveModel mixin with official SDK pattern using ActiveSupport::Concern + ActiveModel::Serializers::JSON - data-handling.md: Fix list_workflows call signature (positional, not kw) - ruby.md, gotchas.md: Fix require paths to use 'temporalio/activity' instead of 'temporalio/activity/definition' Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix correctness issues in Ruby reference files - patterns.md: Fix external workflow signal to use class method ref (TargetWorkflow.data_ready instead of TargetWorkflow, :data_ready) - patterns.md: Add ? suffix to all_handlers_finished (Ruby boolean convention) - ruby.md: Add 'default' namespace to Client.connect calls Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add Ruby to all language references in SKILL.md and core files - SKILL.md: Add "Temporal Ruby" trigger phrase to description - SKILL.md: Update Overview to list Ruby as supported language - SKILL.md: Add Ruby entry to Getting Started guide - core/determinism.md: Add Ruby SDK Protection Mechanism entry (Illegal Call Tracing via TracePoint + Durable Fiber Scheduler) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Apply suggestions from code review Co-authored-by: Bart de Water <118401830+bdewater-thatch@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> * Apply suggestions from code review Co-authored-by: Bart de Water <118401830+bdewater-thatch@users.noreply.github.com> Co-authored-by: Donald Pinckney <donald_pinckney@icloud.com> * Apply suggestion from @chris-olszewski Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> * copy over sample code * Remove useless section, mention Mutex * cleanup mutex mentions * Clean up transitive NDE section * Menial changes to align to python structure * Add Workflow Init section to Ruby advanced-features Document the workflow_init class method (Ruby's equivalent of Python's @workflow.init) for initializing workflow state before signal/update handlers run. Parallels the Python reference's Workflow Init section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document graceful_shutdown_period in Ruby Worker Tuning Add the graceful_shutdown_period worker option (Ruby's equivalent of Python's graceful_shutdown_timeout) to the Worker Tuning section, with an explanation of the worker shutdown sequence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Propagate cancellation in Ruby activity-error handling Update the Handling Activity Errors example to re-raise when Temporalio::Error.canceled? is true (Ruby's equivalent of Python's is_cancelled_exception), so a canceled activity cancels the workflow rather than failing it. Also clarify that only ApplicationError fails a workflow; other exceptions only fail/retry the workflow task. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Align Ruby Workflow Failure section to Python Replace the workflow_failure_exception_type / worker-option examples (misaligned with Python and already covered in advanced-features.md) with Python's example of raising an ApplicationError to deliberately fail a workflow. Add the terse note about not using non_retryable inside a workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add logger configuration to Ruby observability Document configuring the logger via Client.connect (logger: kwarg), which is used by both Temporalio::Workflow.logger and the activity logger. Parallels Python's Customizing Logger Configuration section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Make Ruby Saga compensations cancellation-proof Run saga compensations with a detached Temporalio::Cancellation so they still execute when the workflow is canceled mid-saga. Previously they used the workflow cancellation, which is already canceled at that point, so the compensation activities would be canceled before starting. This is the Ruby equivalent of Python's asyncio.shield. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document patched() memoization caveat in Ruby versioning Note that Temporalio::Workflow.patched memoizes per patch ID, so it can't be used reliably in loops; append a sequence number to the patch ID per iteration. This behavior is shared with Python and .NET. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add default versioning behavior to Ruby worker versioning Document configuring default_versioning_behavior on Temporalio::Worker::DeploymentOptions, paralleling Python's Worker Configuration with Default Behavior section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix worker versioning config API names in Ruby docs The Configuring Workers for Versioning example used class/kwarg names that don't exist in the SDK. Correct them to deployment_options:, Temporalio::Worker::DeploymentOptions, and Temporalio::WorkerDeploymentVersion, matching the actual API and the Worker Configuration with Default Behavior example. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix worker concurrency config in Ruby Worker Tuning max_concurrent_workflow_tasks and max_concurrent_activities are not valid Worker.new kwargs. Use the tuner: option with Temporalio::Worker::Tuner.create_fixed(workflow_slots:, activity_slots:) to control concurrent execution slots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Align Ruby Workflow Init title with Python Rename the section to 'Workflow Init Decorator' to match the Python reference's heading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Structure Ruby Metrics to match Python Split the flat Metrics section into 'Enabling SDK Metrics' and 'Key SDK Metrics' subsections, matching the Python reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Bart de Water <118401830+bdewater-thatch@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com>
318 lines
8.8 KiB
Markdown
318 lines
8.8 KiB
Markdown
# Ruby SDK Versioning
|
|
|
|
For conceptual overview, see `references/core/versioning.md`.
|
|
|
|
## Patching API
|
|
|
|
### The patched() Method
|
|
|
|
`Temporalio::Workflow.patched('my-patch')` returns `true`/`false` to branch between new and old code paths:
|
|
|
|
```ruby
|
|
class MyWorkflow < Temporalio::Workflow::Definition
|
|
def execute
|
|
if Temporalio::Workflow.patched('my-patch')
|
|
# New code path
|
|
Temporalio::Workflow.execute_activity(
|
|
PostPatchActivity,
|
|
start_to_close_timeout: 100
|
|
)
|
|
else
|
|
# Old code path (for replay of existing workflows)
|
|
Temporalio::Workflow.execute_activity(
|
|
PrePatchActivity,
|
|
start_to_close_timeout: 100
|
|
)
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
**How it works:**
|
|
- For new executions: `patched()` returns `true` and records a marker in the Workflow history
|
|
- For replay with the marker: `patched()` returns `true` (history includes this patch)
|
|
- For replay without the marker: `patched()` returns `false` (history predates this patch)
|
|
|
|
**Note:** The `patched()` return value is memoized per patch ID. This means you cannot reliably use `patched()` in loops—it will return the same value every iteration. Workaround: append a sequence number to the patch ID for each iteration (e.g., `"my-change-#{i}"`).
|
|
|
|
### Three-Step Patching Process
|
|
|
|
**Warning:** Failing to follow this process correctly will result in non-determinism errors for in-flight workflows.
|
|
|
|
**Step 1: Patch in New Code**
|
|
|
|
Add the patch with both old and new code paths:
|
|
|
|
```ruby
|
|
class OrderWorkflow < Temporalio::Workflow::Definition
|
|
def execute(order)
|
|
if Temporalio::Workflow.patched('add-fraud-check')
|
|
# New: Run fraud check before payment
|
|
Temporalio::Workflow.execute_activity(
|
|
CheckFraudActivity,
|
|
order,
|
|
start_to_close_timeout: 120
|
|
)
|
|
end
|
|
|
|
# Original payment logic runs for both paths
|
|
Temporalio::Workflow.execute_activity(
|
|
ProcessPaymentActivity,
|
|
order,
|
|
start_to_close_timeout: 300
|
|
)
|
|
end
|
|
end
|
|
```
|
|
|
|
**Step 2: Deprecate the Patch**
|
|
|
|
Once all pre-patch Workflow Executions have completed, remove the old code and use `deprecate_patch()`:
|
|
|
|
```ruby
|
|
class OrderWorkflow < Temporalio::Workflow::Definition
|
|
def execute(order)
|
|
Temporalio::Workflow.deprecate_patch('add-fraud-check')
|
|
|
|
# Only new code remains
|
|
Temporalio::Workflow.execute_activity(
|
|
CheckFraudActivity,
|
|
order,
|
|
start_to_close_timeout: 120
|
|
)
|
|
|
|
Temporalio::Workflow.execute_activity(
|
|
ProcessPaymentActivity,
|
|
order,
|
|
start_to_close_timeout: 300
|
|
)
|
|
end
|
|
end
|
|
```
|
|
|
|
**Step 3: Remove the Patch**
|
|
|
|
After all workflows with the deprecated patch marker have completed, remove the `deprecate_patch()` call entirely:
|
|
|
|
```ruby
|
|
class OrderWorkflow < Temporalio::Workflow::Definition
|
|
def execute(order)
|
|
Temporalio::Workflow.execute_activity(
|
|
CheckFraudActivity,
|
|
order,
|
|
start_to_close_timeout: 120
|
|
)
|
|
|
|
Temporalio::Workflow.execute_activity(
|
|
ProcessPaymentActivity,
|
|
order,
|
|
start_to_close_timeout: 300
|
|
)
|
|
end
|
|
end
|
|
```
|
|
|
|
### Query Filters for Finding Workflows by Version
|
|
|
|
```bash
|
|
# Find running workflows with a specific patch
|
|
temporal workflow list --query \
|
|
'WorkflowType = "OrderWorkflow" AND ExecutionStatus = "Running" AND TemporalChangeVersion = "add-fraud-check"'
|
|
|
|
# Find running workflows without any patch (pre-patch versions)
|
|
temporal workflow list --query \
|
|
'WorkflowType = "OrderWorkflow" AND ExecutionStatus = "Running" AND TemporalChangeVersion IS NULL'
|
|
```
|
|
|
|
## Workflow Type Versioning
|
|
|
|
For incompatible changes, create a new Workflow Type by duplicating the class:
|
|
|
|
```ruby
|
|
class MyWorkflow < Temporalio::Workflow::Definition
|
|
def execute
|
|
# Original implementation
|
|
Temporalio::Workflow.execute_activity(
|
|
OriginalActivity,
|
|
start_to_close_timeout: 100
|
|
)
|
|
end
|
|
end
|
|
|
|
class MyWorkflowV2 < Temporalio::Workflow::Definition
|
|
def execute
|
|
# New implementation with incompatible changes
|
|
Temporalio::Workflow.execute_activity(
|
|
NewActivity,
|
|
start_to_close_timeout: 100
|
|
)
|
|
end
|
|
end
|
|
```
|
|
|
|
Register both with the Worker:
|
|
|
|
```ruby
|
|
worker = Temporalio::Worker.new(
|
|
client: client,
|
|
task_queue: 'my-task-queue',
|
|
workflows: [MyWorkflow, MyWorkflowV2],
|
|
activities: [OriginalActivity, NewActivity]
|
|
)
|
|
```
|
|
|
|
Update client code to start new workflows with the new type:
|
|
|
|
```ruby
|
|
# Old workflows continue on MyWorkflow
|
|
# New workflows use MyWorkflowV2
|
|
handle = client.start_workflow(
|
|
MyWorkflowV2,
|
|
input,
|
|
id: SecureRandom.uuid,
|
|
task_queue: 'my-task-queue'
|
|
)
|
|
```
|
|
|
|
Check for open executions before removing the old type:
|
|
|
|
```bash
|
|
temporal workflow list --query 'WorkflowType = "MyWorkflow" AND ExecutionStatus = "Running"'
|
|
```
|
|
|
|
## Worker Versioning
|
|
|
|
Worker Versioning manages versions at the deployment level, allowing multiple Worker versions to run simultaneously. Requires Ruby SDK v0.5.0+.
|
|
|
|
### Key Concepts
|
|
|
|
**Worker Deployment**: A logical service grouping similar Workers together (e.g., "order-processor"). All versions of your code live under this umbrella.
|
|
|
|
**Worker Deployment Version**: A specific snapshot of your code identified by a deployment name and Build ID (e.g., "order-processor:v1.0" or "order-processor:abc123").
|
|
|
|
### Configuring Workers for Versioning
|
|
|
|
```ruby
|
|
worker = Temporalio::Worker.new(
|
|
client: client,
|
|
task_queue: 'my-task-queue',
|
|
workflows: [MyWorkflow],
|
|
activities: [MyActivity],
|
|
deployment_options: Temporalio::Worker::DeploymentOptions.new(
|
|
version: Temporalio::WorkerDeploymentVersion.new(
|
|
deployment_name: 'my-service',
|
|
build_id: 'v1.0.0' # or git commit hash
|
|
),
|
|
use_worker_versioning: true
|
|
)
|
|
)
|
|
```
|
|
|
|
### PINNED vs AUTO_UPGRADE Behaviors
|
|
|
|
**PINNED Behavior**
|
|
|
|
Workflows stay locked to their original Worker version. Set on the workflow definition:
|
|
|
|
```ruby
|
|
class StableWorkflow < Temporalio::Workflow::Definition
|
|
workflow_versioning_behavior :pinned
|
|
|
|
def execute
|
|
Temporalio::Workflow.execute_activity(
|
|
ProcessOrderActivity,
|
|
start_to_close_timeout: 300
|
|
)
|
|
end
|
|
end
|
|
```
|
|
|
|
**When to use PINNED:**
|
|
- Short-running workflows (minutes to hours)
|
|
- Consistency is critical (e.g., financial transactions)
|
|
- You want to eliminate version compatibility complexity
|
|
- Building new applications and want simplest development experience
|
|
|
|
**AUTO_UPGRADE Behavior**
|
|
|
|
Workflows can move to newer versions:
|
|
|
|
```ruby
|
|
class LongRunningWorkflow < Temporalio::Workflow::Definition
|
|
workflow_versioning_behavior :auto_upgrade
|
|
|
|
def execute
|
|
# This workflow may be picked up by a newer Worker version
|
|
Temporalio::Workflow.execute_activity(
|
|
ProcessActivity,
|
|
start_to_close_timeout: 300
|
|
)
|
|
end
|
|
end
|
|
```
|
|
|
|
**When to use AUTO_UPGRADE:**
|
|
- Long-running workflows (weeks or months)
|
|
- Workflows need to benefit from bug fixes during execution
|
|
- Migrating from traditional rolling deployments
|
|
- You are already using patching APIs for version transitions
|
|
|
|
**Important:** AUTO_UPGRADE workflows still need patching to handle version transitions safely since they can move between Worker versions.
|
|
|
|
### Worker Configuration with Default Behavior
|
|
|
|
```ruby
|
|
worker = Temporalio::Worker.new(
|
|
client: client,
|
|
task_queue: 'orders-task-queue',
|
|
workflows: [OrderWorkflow],
|
|
activities: [ProcessOrderActivity],
|
|
deployment_options: Temporalio::Worker::DeploymentOptions.new(
|
|
version: Temporalio::WorkerDeploymentVersion.new(
|
|
deployment_name: 'order-service',
|
|
build_id: ENV.fetch('BUILD_ID')
|
|
),
|
|
use_worker_versioning: true,
|
|
default_versioning_behavior: Temporalio::VersioningBehavior::PINNED
|
|
)
|
|
)
|
|
```
|
|
|
|
### Deployment Strategies
|
|
|
|
**Blue-Green Deployments**
|
|
|
|
Maintain two environments and switch traffic between them:
|
|
1. Deploy new code to idle environment
|
|
2. Run tests and validation
|
|
3. Switch traffic to new environment
|
|
4. Keep old environment for instant rollback
|
|
|
|
**Rainbow Deployments**
|
|
|
|
Multiple versions run simultaneously:
|
|
- New workflows use latest version
|
|
- Existing workflows complete on their original version
|
|
- Add new versions alongside existing ones
|
|
- Gradually sunset old versions as workflows complete
|
|
|
|
This works well with Kubernetes where you manage multiple ReplicaSets running different Worker versions.
|
|
|
|
### Querying Workflows by Worker Version
|
|
|
|
```bash
|
|
# Find workflows on a specific Worker version
|
|
temporal workflow list --query \
|
|
'TemporalWorkerDeploymentVersion = "my-service:v1.0.0" AND ExecutionStatus = "Running"'
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Check for open executions** before removing old code paths
|
|
2. **Use descriptive patch IDs** that explain the change (e.g., "add-fraud-check" not "patch-1")
|
|
3. **Deploy patches incrementally**: patch, deprecate, remove
|
|
4. **Use PINNED for short workflows** to simplify version management
|
|
5. **Use AUTO_UPGRADE with patching** for long-running workflows that need updates
|
|
6. **Generate Build IDs from code** (git hash) to ensure changes produce new versions
|
|
7. **Avoid rolling deployments** for high-availability services with long-running workflows
|