@temporal-architect/claude-plugin 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/package.json +37 -0
- package/skills/MANIFEST.json +373 -0
- package/skills/MANIFEST.md +121 -0
- package/skills/temporal-architect/SKILL.md +99 -0
- package/skills/temporal-architect/reference/decomposition.md +78 -0
- package/skills/temporal-architect-author-go/README.md +16 -0
- package/skills/temporal-architect-author-go/SKILL.md +191 -0
- package/skills/temporal-architect-author-go/SUBAGENT_ADOPTION.md +161 -0
- package/skills/temporal-architect-author-go/reference/activity-call.md +73 -0
- package/skills/temporal-architect-author-go/reference/activity-def.md +54 -0
- package/skills/temporal-architect-author-go/reference/assignment.md +36 -0
- package/skills/temporal-architect-author-go/reference/await-all.md +104 -0
- package/skills/temporal-architect-author-go/reference/await-one.md +193 -0
- package/skills/temporal-architect-author-go/reference/await-timer.md +35 -0
- package/skills/temporal-architect-author-go/reference/close.md +71 -0
- package/skills/temporal-architect-author-go/reference/composite-patterns.md +176 -0
- package/skills/temporal-architect-author-go/reference/condition.md +56 -0
- package/skills/temporal-architect-author-go/reference/control-flow.md +151 -0
- package/skills/temporal-architect-author-go/reference/dependency-resolution.md +29 -0
- package/skills/temporal-architect-author-go/reference/detach.md +52 -0
- package/skills/temporal-architect-author-go/reference/heartbeat.md +84 -0
- package/skills/temporal-architect-author-go/reference/nexus-service-def.md +73 -0
- package/skills/temporal-architect-author-go/reference/nexus.md +35 -0
- package/skills/temporal-architect-author-go/reference/options.md +138 -0
- package/skills/temporal-architect-author-go/reference/promise.md +73 -0
- package/skills/temporal-architect-author-go/reference/proto-driven.md +197 -0
- package/skills/temporal-architect-author-go/reference/query-handler.md +34 -0
- package/skills/temporal-architect-author-go/reference/signal-handler.md +73 -0
- package/skills/temporal-architect-author-go/reference/three-layer-testing.md +173 -0
- package/skills/temporal-architect-author-go/reference/types.md +72 -0
- package/skills/temporal-architect-author-go/reference/update-handler.md +64 -0
- package/skills/temporal-architect-author-go/reference/worker.md +215 -0
- package/skills/temporal-architect-author-go/reference/workflow-call.md +37 -0
- package/skills/temporal-architect-author-go/reference/workflow-def.md +45 -0
- package/skills/temporal-architect-author-infra/README.md +16 -0
- package/skills/temporal-architect-author-infra/SKILL.md +132 -0
- package/skills/temporal-architect-author-infra/reference/tcld.md +112 -0
- package/skills/temporal-architect-author-infra/reference/terraform.md +125 -0
- package/skills/temporal-architect-design/README.md +16 -0
- package/skills/temporal-architect-design/SKILL.md +224 -0
- package/skills/temporal-architect-design/reference/LANGUAGE.md +5 -0
- package/skills/temporal-architect-design/reference/anti-patterns.md +332 -0
- package/skills/temporal-architect-design/reference/common-errors.md +88 -0
- package/skills/temporal-architect-design/reference/core-principles.md +52 -0
- package/skills/temporal-architect-design/reference/design-checklist.md +59 -0
- package/skills/temporal-architect-design/reference/namespaces.md +84 -0
- package/skills/temporal-architect-design/reference/notation-examples.md +304 -0
- package/skills/temporal-architect-design/reference/notation-reference.md +70 -0
- package/skills/temporal-architect-design/reference/primitives-reference.md +65 -0
- package/skills/temporal-architect-design/reference/project-discovery-subagent.md +80 -0
- package/skills/temporal-architect-design/reference/reverse-engineering.md +53 -0
- package/skills/temporal-architect-design/reference/twf-conventions.md +43 -0
- package/skills/temporal-architect-design/reference/workflow-boundaries.md +43 -0
- package/skills/temporal-architect-design/topics/activities-advanced.md +358 -0
- package/skills/temporal-architect-design/topics/activities-advanced.twf +107 -0
- package/skills/temporal-architect-design/topics/child-workflows.md +347 -0
- package/skills/temporal-architect-design/topics/child-workflows.twf +171 -0
- package/skills/temporal-architect-design/topics/long-running.md +230 -0
- package/skills/temporal-architect-design/topics/long-running.twf +100 -0
- package/skills/temporal-architect-design/topics/nexus.md +248 -0
- package/skills/temporal-architect-design/topics/nexus.twf +148 -0
- package/skills/temporal-architect-design/topics/patterns.md +469 -0
- package/skills/temporal-architect-design/topics/patterns.twf +346 -0
- package/skills/temporal-architect-design/topics/promises-conditions.md +179 -0
- package/skills/temporal-architect-design/topics/promises-conditions.twf +213 -0
- package/skills/temporal-architect-design/topics/signals-queries-updates.md +319 -0
- package/skills/temporal-architect-design/topics/signals-queries-updates.twf +234 -0
- package/skills/temporal-architect-design/topics/task-queues.md +205 -0
- package/skills/temporal-architect-design/topics/task-queues.twf +184 -0
- package/skills/temporal-architect-design/topics/testing.md +437 -0
- package/skills/temporal-architect-design/topics/testing.twf +177 -0
- package/skills/temporal-architect-design/topics/timers-scheduling.md +131 -0
- package/skills/temporal-architect-design/topics/timers-scheduling.twf +129 -0
- package/skills/temporal-architect-design/topics/versioning.md +434 -0
- package/skills/temporal-architect-design/topics/versioning.twf +174 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Timers and Scheduling
|
|
2
|
+
|
|
3
|
+
> **Example:** [`timers-scheduling.twf`](./timers-scheduling.twf)
|
|
4
|
+
|
|
5
|
+
Durable timing primitives for delays, deadlines, and recurring execution.
|
|
6
|
+
|
|
7
|
+
## Timers
|
|
8
|
+
|
|
9
|
+
Durable sleep that survives worker restarts, deployments, and failures.
|
|
10
|
+
|
|
11
|
+
### Basic Timer
|
|
12
|
+
|
|
13
|
+
```twf
|
|
14
|
+
workflow DelayedNotification(userId: string, delay: duration):
|
|
15
|
+
# Durable sleep - workflow pauses but state is preserved
|
|
16
|
+
await timer(delay)
|
|
17
|
+
|
|
18
|
+
activity SendNotification(userId)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Timer Considerations
|
|
22
|
+
|
|
23
|
+
| Aspect | Guidance |
|
|
24
|
+
|--------|----------|
|
|
25
|
+
| **Durability** | Timer survives worker restarts; workflow resumes when timer fires |
|
|
26
|
+
| **Precision** | Not precise to the millisecond; expect seconds of variance |
|
|
27
|
+
| **History** | Each timer adds to workflow history; avoid very frequent short timers |
|
|
28
|
+
| **Cancellation** | Timers can be cancelled if workflow is cancelled |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Deadlines and Timeouts
|
|
33
|
+
|
|
34
|
+
### Workflow-Level Deadline
|
|
35
|
+
|
|
36
|
+
```twf
|
|
37
|
+
workflow OrderFulfillment(order: Order) -> (OrderResult):
|
|
38
|
+
# Entire workflow must complete within deadline (SDK-level config)
|
|
39
|
+
# workflow_timeout: 7d
|
|
40
|
+
|
|
41
|
+
activity ValidateRetailOrder(order)
|
|
42
|
+
await signal PaymentReceived
|
|
43
|
+
activity ShipRetailOrder(order)
|
|
44
|
+
close complete(OrderResult{status: "completed"})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Operation Deadline Pattern
|
|
48
|
+
|
|
49
|
+
```twf
|
|
50
|
+
workflow ProcessWithDeadline(data: Data) -> (Result):
|
|
51
|
+
# Race between operation and deadline
|
|
52
|
+
await one:
|
|
53
|
+
activity LongOperation(data) -> result:
|
|
54
|
+
close complete(Result{success: true, data: result})
|
|
55
|
+
timer(1h):
|
|
56
|
+
activity Cleanup(data)
|
|
57
|
+
close fail(Result{success: false, error: "deadline exceeded"})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> **`await one` does not cancel the loser.** If the timer wins, `LongOperation` is **not** cancelled — it keeps running in the background until the workflow run ends. `await one` is "first to complete wins," not "winner cancels the rest." If the losing operation must actually stop (release a lock, stop billing), you need an explicit cancellation/cleanup activity — the race alone won't do it.
|
|
61
|
+
|
|
62
|
+
### Timeout on Signal Wait
|
|
63
|
+
|
|
64
|
+
```twf
|
|
65
|
+
workflow ApprovalWorkflow(request: Request) -> (Decision):
|
|
66
|
+
activity NotifyApprovers(request)
|
|
67
|
+
|
|
68
|
+
await one:
|
|
69
|
+
signal Approved:
|
|
70
|
+
close complete(Decision{status: "approved"})
|
|
71
|
+
signal Rejected:
|
|
72
|
+
close complete(Decision{status: "rejected"})
|
|
73
|
+
timer(7d):
|
|
74
|
+
activity NotifyExpired(request)
|
|
75
|
+
close complete(Decision{status: "expired"})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Scheduling Patterns
|
|
81
|
+
|
|
82
|
+
### Periodic Execution Within Workflow
|
|
83
|
+
|
|
84
|
+
```twf
|
|
85
|
+
workflow Heartbeat(resourceId: string):
|
|
86
|
+
for:
|
|
87
|
+
activity CheckHealth(resourceId)
|
|
88
|
+
await timer(5m)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Polling with Backoff
|
|
92
|
+
|
|
93
|
+
```twf
|
|
94
|
+
workflow WaitForResource(resourceId: string) -> (Resource):
|
|
95
|
+
backoff = 1s
|
|
96
|
+
max_backoff = 5m
|
|
97
|
+
|
|
98
|
+
for:
|
|
99
|
+
activity CheckResource(resourceId) -> resource
|
|
100
|
+
if resource.ready:
|
|
101
|
+
close complete(resource)
|
|
102
|
+
|
|
103
|
+
await timer(backoff)
|
|
104
|
+
backoff = min(backoff * 2, max_backoff)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Deadline with Periodic Check
|
|
108
|
+
|
|
109
|
+
```twf
|
|
110
|
+
workflow WaitForCompletion(jobId: string) -> (JobResult):
|
|
111
|
+
for:
|
|
112
|
+
activity GetJobStatus(jobId) -> status
|
|
113
|
+
if status.complete:
|
|
114
|
+
close complete(JobResult{status: "complete", data: status.data})
|
|
115
|
+
|
|
116
|
+
await one:
|
|
117
|
+
timer(30s):
|
|
118
|
+
# Continue polling
|
|
119
|
+
timer(2h):
|
|
120
|
+
close fail(JobResult{status: "timeout"})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Schedules (Cron Workflows)
|
|
126
|
+
|
|
127
|
+
Temporal Schedules execute workflows on a recurring basis (cron expressions, intervals, calendars). Schedules are **platform configuration**, not workflow design — they define *when* to start a workflow, not *how* it runs.
|
|
128
|
+
|
|
129
|
+
Schedule configuration (specs, overlap policies, catchup windows, timezones) is managed through the Temporal CLI or SDK, not TWF notation. See [Temporal Schedules documentation](https://docs.temporal.io/workflows#schedule) for details.
|
|
130
|
+
|
|
131
|
+
**Design implication:** A scheduled workflow should be designed like any other workflow — idempotent, with continue-as-new if long-running. The schedule itself is an external trigger, not part of the workflow's logic.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Source: timers-scheduling.md
|
|
2
|
+
# Patterns: timer delay, deadline, polling with backoff, periodic
|
|
3
|
+
|
|
4
|
+
# --- Basic timer: delayed notification ---
|
|
5
|
+
|
|
6
|
+
workflow DelayedNotification(userId: string, delay: duration):
|
|
7
|
+
# Durable sleep - workflow pauses but state is preserved
|
|
8
|
+
await timer(delay)
|
|
9
|
+
activity SendNotification(userId)
|
|
10
|
+
|
|
11
|
+
# --- Deadline pattern: race between operation and timer ---
|
|
12
|
+
|
|
13
|
+
workflow ProcessWithDeadline(data: Data) -> (Result):
|
|
14
|
+
await one:
|
|
15
|
+
activity LongOperation(data) -> result:
|
|
16
|
+
close complete(Result{success: true, data: result})
|
|
17
|
+
timer(1h):
|
|
18
|
+
activity Cleanup(data)
|
|
19
|
+
close fail(Result{success: false, error: "deadline exceeded"})
|
|
20
|
+
|
|
21
|
+
# --- Periodic execution within workflow ---
|
|
22
|
+
|
|
23
|
+
workflow HealthMonitor(resourceId: string):
|
|
24
|
+
count = 0
|
|
25
|
+
|
|
26
|
+
for:
|
|
27
|
+
activity CheckHealth(resourceId)
|
|
28
|
+
await timer(5m)
|
|
29
|
+
count = count + 1
|
|
30
|
+
if (count > 1000):
|
|
31
|
+
close continue_as_new(resourceId)
|
|
32
|
+
|
|
33
|
+
# --- Polling with backoff ---
|
|
34
|
+
|
|
35
|
+
workflow WaitForResource(resourceId: string) -> (Resource):
|
|
36
|
+
backoff = 1s
|
|
37
|
+
maxBackoff = 60s
|
|
38
|
+
|
|
39
|
+
for:
|
|
40
|
+
activity CheckResource(resourceId) -> resource
|
|
41
|
+
if (resource.ready):
|
|
42
|
+
close complete(resource)
|
|
43
|
+
|
|
44
|
+
await one:
|
|
45
|
+
timer(backoff):
|
|
46
|
+
backoff = min(backoff * 2, maxBackoff)
|
|
47
|
+
timer(30m):
|
|
48
|
+
close fail(Resource{error: "timeout"})
|
|
49
|
+
|
|
50
|
+
# --- Deadline with periodic check ---
|
|
51
|
+
|
|
52
|
+
workflow WaitForCompletion(jobId: string) -> (JobResult):
|
|
53
|
+
checks = 0
|
|
54
|
+
|
|
55
|
+
for:
|
|
56
|
+
activity GetJobStatus(jobId) -> status
|
|
57
|
+
if (status.complete):
|
|
58
|
+
close complete(JobResult{status: "complete", data: status.data})
|
|
59
|
+
|
|
60
|
+
checks = checks + 1
|
|
61
|
+
if (checks > 100):
|
|
62
|
+
close complete(JobResult{status: "timeout"})
|
|
63
|
+
|
|
64
|
+
await timer(30s)
|
|
65
|
+
|
|
66
|
+
# --- Polling with continue-as-new for history management ---
|
|
67
|
+
|
|
68
|
+
workflow LongPoller(resourceId: string, iteration: int) -> (Resource):
|
|
69
|
+
count = 0
|
|
70
|
+
|
|
71
|
+
for:
|
|
72
|
+
activity PollResource(resourceId) -> status
|
|
73
|
+
if (status.ready):
|
|
74
|
+
activity FetchResource(resourceId) -> resource
|
|
75
|
+
close complete(resource)
|
|
76
|
+
|
|
77
|
+
await timer(5s)
|
|
78
|
+
count = count + 1
|
|
79
|
+
if (count >= 500):
|
|
80
|
+
close continue_as_new(resourceId, iteration + 1)
|
|
81
|
+
|
|
82
|
+
# --- Supporting activities ---
|
|
83
|
+
|
|
84
|
+
activity SendNotification(userId: string):
|
|
85
|
+
send(userId)
|
|
86
|
+
|
|
87
|
+
activity LongOperation(data: Data) -> (OperationResult):
|
|
88
|
+
return process(data)
|
|
89
|
+
|
|
90
|
+
activity Cleanup(data: Data):
|
|
91
|
+
cleanup(data)
|
|
92
|
+
|
|
93
|
+
activity CheckHealth(resourceId: string) -> (HealthStatus):
|
|
94
|
+
return check(resourceId)
|
|
95
|
+
|
|
96
|
+
activity CheckResource(resourceId: string) -> (Resource):
|
|
97
|
+
return check(resourceId)
|
|
98
|
+
|
|
99
|
+
activity GetJobStatus(jobId: string) -> (JobStatus):
|
|
100
|
+
return get_status(jobId)
|
|
101
|
+
|
|
102
|
+
activity PollResource(resourceId: string) -> (ResourceStatus):
|
|
103
|
+
return poll(resourceId)
|
|
104
|
+
|
|
105
|
+
activity FetchResource(resourceId: string) -> (Resource):
|
|
106
|
+
return fetch(resourceId)
|
|
107
|
+
|
|
108
|
+
# --- Worker and namespace ---
|
|
109
|
+
|
|
110
|
+
worker schedulingWorker:
|
|
111
|
+
workflow DelayedNotification
|
|
112
|
+
workflow ProcessWithDeadline
|
|
113
|
+
workflow HealthMonitor
|
|
114
|
+
workflow WaitForResource
|
|
115
|
+
workflow WaitForCompletion
|
|
116
|
+
workflow LongPoller
|
|
117
|
+
activity SendNotification
|
|
118
|
+
activity LongOperation
|
|
119
|
+
activity Cleanup
|
|
120
|
+
activity CheckHealth
|
|
121
|
+
activity CheckResource
|
|
122
|
+
activity GetJobStatus
|
|
123
|
+
activity PollResource
|
|
124
|
+
activity FetchResource
|
|
125
|
+
|
|
126
|
+
namespace scheduling:
|
|
127
|
+
worker schedulingWorker
|
|
128
|
+
options:
|
|
129
|
+
task_queue: "scheduling"
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# Workflow Versioning and Evolution
|
|
2
|
+
|
|
3
|
+
> **Example:** [`versioning.twf`](./versioning.twf)
|
|
4
|
+
|
|
5
|
+
Safe strategies for evolving workflows without breaking running executions.
|
|
6
|
+
|
|
7
|
+
## The Versioning Challenge
|
|
8
|
+
|
|
9
|
+
Running workflows may execute for hours, days, or months. When you deploy new code:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Problem:
|
|
13
|
+
1. Workflow V1 starts, runs step A, B
|
|
14
|
+
2. You deploy V2 (changes step B to B')
|
|
15
|
+
3. Worker restarts, replays V1 workflow
|
|
16
|
+
4. Replay expects B but code has B'
|
|
17
|
+
5. Non-determinism error!
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Solution:** Version-aware code that handles both old and new execution paths.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Temporal's Versioning Approaches
|
|
25
|
+
|
|
26
|
+
| Approach | Use Case | Complexity |
|
|
27
|
+
|----------|----------|------------|
|
|
28
|
+
| **Patching API** | Incremental changes to existing workflows | Low |
|
|
29
|
+
| **Worker Versioning** | Major workflow changes, complete rewrites | Medium |
|
|
30
|
+
| **Workflow Type Versioning** | Breaking changes, parallel versions | Higher |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Patching API
|
|
35
|
+
|
|
36
|
+
Add conditional logic to handle old vs new code paths during replay.
|
|
37
|
+
|
|
38
|
+
> **TWF vs SDK:** The `.twf` file ([`versioning.twf`](./versioning.twf)) uses boolean flag parameters for version gating — this is the DSL-level representation. The examples below use `patched("...")` which is the SDK-level API (Go: `workflow.GetVersion()`, Python: `patched()`). Both represent the same concept: branching on whether a workflow execution predates a code change.
|
|
39
|
+
|
|
40
|
+
### Basic Pattern
|
|
41
|
+
|
|
42
|
+
```pseudo
|
|
43
|
+
workflow UnderwritingAddFraudCheck(policy: Policy) -> (PolicyResult):
|
|
44
|
+
activity ValidatePolicy(policy)
|
|
45
|
+
|
|
46
|
+
# Version gate: new code only runs for new executions
|
|
47
|
+
if patched("add-fraud-check"):
|
|
48
|
+
activity FraudCheck(policy) # New step, only for new workflows
|
|
49
|
+
|
|
50
|
+
activity BindPolicy(policy)
|
|
51
|
+
close complete(PolicyResult{status: "complete"})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### How Patching Works
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
New Execution:
|
|
58
|
+
1. patched("add-fraud-check") → true (marks in history)
|
|
59
|
+
2. FraudCheck runs
|
|
60
|
+
3. History: [Validate, Patch:add-fraud-check, FraudCheck, Payment]
|
|
61
|
+
|
|
62
|
+
Replay of Old Execution (started before patch):
|
|
63
|
+
1. History has no patch marker
|
|
64
|
+
2. patched("add-fraud-check") → false
|
|
65
|
+
3. FraudCheck skipped
|
|
66
|
+
4. Replay matches original history
|
|
67
|
+
|
|
68
|
+
Replay of New Execution (started after patch):
|
|
69
|
+
1. History has patch marker
|
|
70
|
+
2. patched("add-fraud-check") → true
|
|
71
|
+
3. FraudCheck runs
|
|
72
|
+
4. Replay matches history
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Patching Examples
|
|
76
|
+
|
|
77
|
+
**Adding a Step:**
|
|
78
|
+
```pseudo
|
|
79
|
+
workflow Process(data: Data) -> (Result):
|
|
80
|
+
activity Step1(data)
|
|
81
|
+
|
|
82
|
+
if patched("v2-add-validation"):
|
|
83
|
+
activity NewValidation(data) # Added in V2
|
|
84
|
+
|
|
85
|
+
activity Step2(data)
|
|
86
|
+
close complete(Result{})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Removing a Step:**
|
|
90
|
+
```pseudo
|
|
91
|
+
workflow Process(data: Data) -> (Result):
|
|
92
|
+
activity Step1(data)
|
|
93
|
+
|
|
94
|
+
if not patched("v3-remove-legacy"):
|
|
95
|
+
activity LegacyStep(data) # Removed in V3, but runs for old workflows
|
|
96
|
+
|
|
97
|
+
activity Step2(data)
|
|
98
|
+
close complete(Result{})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Changing a Step:**
|
|
102
|
+
```pseudo
|
|
103
|
+
workflow Process(data: Data) -> (Result):
|
|
104
|
+
activity Step1(data)
|
|
105
|
+
|
|
106
|
+
if patched("v4-improved-processing"):
|
|
107
|
+
activity ImprovedProcessing(data)
|
|
108
|
+
else:
|
|
109
|
+
activity OldProcessing(data)
|
|
110
|
+
|
|
111
|
+
activity Step3(data)
|
|
112
|
+
close complete(Result{})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Deprecating Patches
|
|
116
|
+
|
|
117
|
+
After all old workflows complete, remove patch:
|
|
118
|
+
|
|
119
|
+
> Note: Patch lifecycle management uses SDK-specific APIs. The concept is shown as pseudo-code.
|
|
120
|
+
|
|
121
|
+
```pseudo
|
|
122
|
+
# Phase 1: Add patch (both paths exist)
|
|
123
|
+
if patched("add-feature"):
|
|
124
|
+
activity NewFeature()
|
|
125
|
+
|
|
126
|
+
# Phase 2: After all old workflows done, simplify
|
|
127
|
+
# (Run deprecate_patch to verify no old workflows)
|
|
128
|
+
if deprecated_patch("add-feature"):
|
|
129
|
+
pass # Old path, will error if any old workflows still running
|
|
130
|
+
activity NewFeature()
|
|
131
|
+
|
|
132
|
+
# Phase 3: Remove patch code entirely
|
|
133
|
+
activity NewFeature()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Worker Versioning (Build IDs)
|
|
139
|
+
|
|
140
|
+
Route workflows to workers running compatible code versions.
|
|
141
|
+
|
|
142
|
+
### Declaring the strategy in `.twf`
|
|
143
|
+
|
|
144
|
+
> **TWF vs SDK:** A worker instantiation declares *which* versioning strategy a worker pool uses via the `versioning` option — the design-altitude decision. The build-id registration, ramping, and routing mechanics below stay SDK/CLI-level (code-scale).
|
|
145
|
+
|
|
146
|
+
```twf
|
|
147
|
+
namespace orders:
|
|
148
|
+
worker orderTypes
|
|
149
|
+
options:
|
|
150
|
+
task_queue: "orderProcessing"
|
|
151
|
+
versioning: build_id # none | build_id | deployment
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`versioning` expresses *intent* — the strategy a worker pool follows — not concrete Build IDs or deployment names (those are deploy-time inputs, never `.twf` content):
|
|
155
|
+
|
|
156
|
+
| Value | Meaning |
|
|
157
|
+
|-------|---------|
|
|
158
|
+
| `none` | Unversioned workers (default) |
|
|
159
|
+
| `build_id` | Build ID–based worker versioning |
|
|
160
|
+
| `deployment` | Worker Deployment–based versioning |
|
|
161
|
+
|
|
162
|
+
Enum values are bare idents — `build_id`, not `build-id` or `"build_id"`.
|
|
163
|
+
|
|
164
|
+
### Concept
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
┌─────────────────────────────────────────────────────┐
|
|
168
|
+
│ Task Queue │
|
|
169
|
+
├─────────────────────────────────────────────────────┤
|
|
170
|
+
│ Build ID: 1.0 │ Build ID: 2.0 │ Build ID: 3.0 │
|
|
171
|
+
│ (default) │ (compatible) │ (latest) │
|
|
172
|
+
└────────┬────────┴────────┬────────┴────────┬───────┘
|
|
173
|
+
│ │ │
|
|
174
|
+
┌───▼───┐ ┌────▼────┐ ┌────▼────┐
|
|
175
|
+
│Worker │ │ Worker │ │ Worker │
|
|
176
|
+
│ 1.0 │ │ 2.0 │ │ 3.0 │
|
|
177
|
+
└───────┘ └─────────┘ └─────────┘
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Configuration
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Register build ID with task queue
|
|
184
|
+
temporal task-queue update-build-ids add-new-default \
|
|
185
|
+
--task-queue main-queue \
|
|
186
|
+
--build-id "v2.0"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> Note: Worker configuration is SDK-level code.
|
|
190
|
+
|
|
191
|
+
```pseudo
|
|
192
|
+
# Worker identifies its build ID
|
|
193
|
+
worker = Worker(
|
|
194
|
+
task_queue: "main-queue",
|
|
195
|
+
build_id: "v2.0",
|
|
196
|
+
workflows: [UnderwritingAddFraudCheck],
|
|
197
|
+
activities: [ValidatePolicy, FraudCheck, BindPolicy]
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Version Sets
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Create version set: v1.0 and v1.1 are compatible
|
|
205
|
+
temporal task-queue update-build-ids add-new-compatible \
|
|
206
|
+
--task-queue main-queue \
|
|
207
|
+
--build-id "v1.1" \
|
|
208
|
+
--existing-compatible-build-id "v1.0"
|
|
209
|
+
|
|
210
|
+
# New version set: v2.0 is NOT compatible with v1.x
|
|
211
|
+
temporal task-queue update-build-ids add-new-default \
|
|
212
|
+
--task-queue main-queue \
|
|
213
|
+
--build-id "v2.0"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Routing Behavior
|
|
217
|
+
|
|
218
|
+
| Workflow State | Routed To |
|
|
219
|
+
|----------------|-----------|
|
|
220
|
+
| New workflow | Latest default build ID |
|
|
221
|
+
| Running workflow | Same build ID (or compatible) |
|
|
222
|
+
| Workflow started on v1.0 | v1.0 or v1.1 worker |
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Workflow Type Versioning
|
|
227
|
+
|
|
228
|
+
Create a new workflow type for breaking changes.
|
|
229
|
+
|
|
230
|
+
### Pattern
|
|
231
|
+
|
|
232
|
+
```twf
|
|
233
|
+
# Version 1
|
|
234
|
+
workflow PolicyV1(policy: PolicyV1Input) -> (PolicyV1Result):
|
|
235
|
+
# Original implementation
|
|
236
|
+
...
|
|
237
|
+
|
|
238
|
+
# Version 2 (breaking changes)
|
|
239
|
+
workflow PolicyV2(policy: PolicyV2Input) -> (PolicyV2Result):
|
|
240
|
+
# New implementation with different structure
|
|
241
|
+
...
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Migration Strategy
|
|
245
|
+
|
|
246
|
+
> Note: API routing logic is application-level code, not TWF notation.
|
|
247
|
+
|
|
248
|
+
```pseudo
|
|
249
|
+
# API layer routes to appropriate version
|
|
250
|
+
function startPolicyWorkflow(policy):
|
|
251
|
+
if policy.version == 1:
|
|
252
|
+
return client.start(PolicyV1, convertToV1(policy))
|
|
253
|
+
else:
|
|
254
|
+
return client.start(PolicyV2, convertToV2(policy))
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### When to Use Workflow Type Versioning
|
|
258
|
+
|
|
259
|
+
| Scenario | Approach |
|
|
260
|
+
|----------|----------|
|
|
261
|
+
| Adding optional step | Patching |
|
|
262
|
+
| Changing activity order | Patching |
|
|
263
|
+
| Complete workflow rewrite | New workflow type |
|
|
264
|
+
| Input/output schema breaking change | New workflow type |
|
|
265
|
+
| Different business logic | New workflow type |
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Versioning Best Practices
|
|
270
|
+
|
|
271
|
+
### 1. Plan for Evolution
|
|
272
|
+
|
|
273
|
+
```pseudo
|
|
274
|
+
# Good: Named constants for versions (SDK-level code)
|
|
275
|
+
PATCH_ADD_FRAUD_CHECK = "2024-01-add-fraud-check"
|
|
276
|
+
PATCH_IMPROVE_VALIDATION = "2024-02-improve-validation"
|
|
277
|
+
|
|
278
|
+
workflow Process(data: Data):
|
|
279
|
+
if patched(PATCH_ADD_FRAUD_CHECK):
|
|
280
|
+
...
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### 2. Test Both Paths
|
|
284
|
+
|
|
285
|
+
```pseudo
|
|
286
|
+
test "workflow handles both old and new path":
|
|
287
|
+
# Test new execution path
|
|
288
|
+
env = TestEnvironment()
|
|
289
|
+
result = env.execute(Workflow, input)
|
|
290
|
+
assert result.includesFraudCheck
|
|
291
|
+
|
|
292
|
+
# Test replay of old execution
|
|
293
|
+
old_history = load("workflow_v1.history")
|
|
294
|
+
replay_result = env.replay(Workflow, old_history)
|
|
295
|
+
assert replay_result.success # No non-determinism
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 3. Document Versions
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
# Workflow: UnderwritingAddFraudCheck
|
|
302
|
+
#
|
|
303
|
+
# Version History:
|
|
304
|
+
# - 2024-01: Added fraud check (patch: add-fraud-check)
|
|
305
|
+
# - 2024-02: Improved validation (patch: improve-validation)
|
|
306
|
+
# - 2024-03: Deprecated old validation (patch: remove-legacy-validation)
|
|
307
|
+
#
|
|
308
|
+
# Active patches: add-fraud-check, improve-validation
|
|
309
|
+
# Deprecated patches: remove-legacy-validation (safe to remove after 2024-04)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 4. Monitor Old Workflows
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# Query for workflows started before patch
|
|
316
|
+
temporal workflow list \
|
|
317
|
+
--query "StartTime < '2024-01-15' AND ExecutionStatus = 'Running'"
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Common Versioning Scenarios
|
|
323
|
+
|
|
324
|
+
### Adding Activity
|
|
325
|
+
|
|
326
|
+
```pseudo
|
|
327
|
+
workflow Process(data: Data):
|
|
328
|
+
activity Existing1(data)
|
|
329
|
+
|
|
330
|
+
if patched("add-new-activity"):
|
|
331
|
+
activity NewActivity(data) # Safe to add
|
|
332
|
+
|
|
333
|
+
activity Existing2(data)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Removing Activity
|
|
337
|
+
|
|
338
|
+
```pseudo
|
|
339
|
+
workflow Process(data: Data):
|
|
340
|
+
activity Existing1(data)
|
|
341
|
+
|
|
342
|
+
if not patched("remove-deprecated"):
|
|
343
|
+
activity DeprecatedActivity(data) # Removed for new, kept for old
|
|
344
|
+
|
|
345
|
+
activity Existing2(data)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Reordering Activities
|
|
349
|
+
|
|
350
|
+
```pseudo
|
|
351
|
+
# Original order: A, B, C
|
|
352
|
+
# New order: A, C, B
|
|
353
|
+
|
|
354
|
+
workflow Process(data: Data):
|
|
355
|
+
activity A(data)
|
|
356
|
+
|
|
357
|
+
if patched("reorder-bc"):
|
|
358
|
+
activity C(data)
|
|
359
|
+
activity B(data)
|
|
360
|
+
else:
|
|
361
|
+
activity B(data)
|
|
362
|
+
activity C(data)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Changing Activity Parameters
|
|
366
|
+
|
|
367
|
+
```pseudo
|
|
368
|
+
workflow Process(data: Data):
|
|
369
|
+
if patched("new-activity-params"):
|
|
370
|
+
activity Enhanced(data, extraParam: true)
|
|
371
|
+
else:
|
|
372
|
+
activity Enhanced(data) # Old signature
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Anti-Patterns
|
|
378
|
+
|
|
379
|
+
### Unguarded Changes
|
|
380
|
+
|
|
381
|
+
```pseudo
|
|
382
|
+
# BAD: Breaking change without version guard
|
|
383
|
+
workflow Process(data: Data):
|
|
384
|
+
activity Step1(data)
|
|
385
|
+
# Removed Step2 without patch - breaks replay!
|
|
386
|
+
activity Step3(data)
|
|
387
|
+
|
|
388
|
+
# GOOD: Version-guarded removal
|
|
389
|
+
workflow Process(data: Data):
|
|
390
|
+
activity Step1(data)
|
|
391
|
+
if not patched("remove-step2"):
|
|
392
|
+
activity Step2(data)
|
|
393
|
+
activity Step3(data)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Too Many Active Patches
|
|
397
|
+
|
|
398
|
+
```pseudo
|
|
399
|
+
# BAD: Accumulated complexity
|
|
400
|
+
workflow Process(data: Data):
|
|
401
|
+
if patched("v1"):
|
|
402
|
+
if patched("v2"):
|
|
403
|
+
if patched("v3"):
|
|
404
|
+
...
|
|
405
|
+
|
|
406
|
+
# GOOD: Consolidate when safe, or use worker versioning
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Forgetting to Deprecate
|
|
410
|
+
|
|
411
|
+
```pseudo
|
|
412
|
+
# BAD: Old patch code lives forever
|
|
413
|
+
if patched("feature-from-2020"): # All workflows with this are done!
|
|
414
|
+
...
|
|
415
|
+
|
|
416
|
+
# GOOD: Clean up after old workflows complete
|
|
417
|
+
# 1. Verify no running workflows need old path
|
|
418
|
+
# 2. Replace with deprecated_patch
|
|
419
|
+
# 3. Remove patch code after verification
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Version Migration Checklist
|
|
425
|
+
|
|
426
|
+
- [ ] Identify all changes from current version
|
|
427
|
+
- [ ] Classify each change (additive, removal, modification)
|
|
428
|
+
- [ ] Add appropriate patches for each change
|
|
429
|
+
- [ ] Write replay tests against old histories
|
|
430
|
+
- [ ] Deploy with both code paths active
|
|
431
|
+
- [ ] Monitor for non-determinism errors
|
|
432
|
+
- [ ] Wait for old workflows to complete
|
|
433
|
+
- [ ] Remove deprecated patch code
|
|
434
|
+
- [ ] Update documentation
|