@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,234 @@
|
|
|
1
|
+
# Source: signals-queries-updates.md
|
|
2
|
+
# Patterns: signal/query/update declarations, watch blocks, approval flows
|
|
3
|
+
|
|
4
|
+
# --- Signal-driven order workflow ---
|
|
5
|
+
|
|
6
|
+
workflow OrderTrackingWorkflow(orderId: string) -> (OrderResult):
|
|
7
|
+
signal PaymentReceived(transactionId: string, amount: decimal):
|
|
8
|
+
status = "payment_received"
|
|
9
|
+
lastTransactionId = transactionId
|
|
10
|
+
|
|
11
|
+
query GetStatus() -> (string):
|
|
12
|
+
return status
|
|
13
|
+
|
|
14
|
+
query GetProgress() -> (Progress):
|
|
15
|
+
return Progress{status: status, orderId: orderId}
|
|
16
|
+
|
|
17
|
+
activity LoadOrderRecord(orderId) -> order
|
|
18
|
+
|
|
19
|
+
status = "validating"
|
|
20
|
+
activity ValidateOrderRecord(order)
|
|
21
|
+
|
|
22
|
+
status = "awaiting_payment"
|
|
23
|
+
|
|
24
|
+
# Wait for payment signal with timeout
|
|
25
|
+
await one:
|
|
26
|
+
signal PaymentReceived:
|
|
27
|
+
status = "completed"
|
|
28
|
+
close complete(OrderResult{status: "completed"})
|
|
29
|
+
timer(24h):
|
|
30
|
+
status = "payment_timeout"
|
|
31
|
+
close fail(OrderResult{status: "payment_timeout"})
|
|
32
|
+
|
|
33
|
+
# --- Approval flow with multi-target await ---
|
|
34
|
+
|
|
35
|
+
workflow ApprovalWorkflow(request: Request) -> (Decision):
|
|
36
|
+
signal Approved(approver: string):
|
|
37
|
+
approved = true
|
|
38
|
+
approver_name = approver
|
|
39
|
+
|
|
40
|
+
signal Rejected(approver: string, reason: string):
|
|
41
|
+
rejected = true
|
|
42
|
+
reject_reason = reason
|
|
43
|
+
|
|
44
|
+
approved = false
|
|
45
|
+
rejected = false
|
|
46
|
+
approver_name = ""
|
|
47
|
+
reject_reason = ""
|
|
48
|
+
|
|
49
|
+
activity NotifyApprovers(request)
|
|
50
|
+
|
|
51
|
+
# Wait for either Approved or Rejected signal, or timeout
|
|
52
|
+
await one:
|
|
53
|
+
signal Approved:
|
|
54
|
+
close complete(Decision{status: "approved", approver: approver_name})
|
|
55
|
+
signal Rejected:
|
|
56
|
+
close fail(Decision{status: "rejected", reason: reject_reason})
|
|
57
|
+
timer(7d):
|
|
58
|
+
activity NotifyExpired(request)
|
|
59
|
+
close fail(Decision{status: "expired"})
|
|
60
|
+
|
|
61
|
+
# --- Batch collector with signal accumulation ---
|
|
62
|
+
|
|
63
|
+
workflow BatchCollector(batchId: string) -> (BatchResult):
|
|
64
|
+
signal AddItem(item: Item):
|
|
65
|
+
itemCount = itemCount + 1
|
|
66
|
+
|
|
67
|
+
signal CompleteBatch():
|
|
68
|
+
completed = true
|
|
69
|
+
|
|
70
|
+
itemCount = 0
|
|
71
|
+
completed = false
|
|
72
|
+
|
|
73
|
+
for:
|
|
74
|
+
await one:
|
|
75
|
+
signal CompleteBatch:
|
|
76
|
+
break
|
|
77
|
+
timer(1h):
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
activity RunBatchProcessing(batchId, itemCount) -> result
|
|
81
|
+
close complete(BatchResult{itemCount: itemCount, result: result})
|
|
82
|
+
|
|
83
|
+
# --- Update-driven subscription workflow ---
|
|
84
|
+
# Updates are read-write: caller sends data and blocks for a result.
|
|
85
|
+
|
|
86
|
+
workflow SubscriptionWorkflow(userId: string):
|
|
87
|
+
signal Cancel():
|
|
88
|
+
cancelled = true
|
|
89
|
+
|
|
90
|
+
# Simple update: immediate state mutation + return
|
|
91
|
+
update AddCredits(amount: int) -> (CreditResult):
|
|
92
|
+
credits = credits + amount
|
|
93
|
+
return CreditResult{total: credits}
|
|
94
|
+
|
|
95
|
+
# Update with activity: validates before mutating, caller blocks for the full round-trip
|
|
96
|
+
update ChangePlan(newPlan: string) -> (ChangeResult):
|
|
97
|
+
activity ValidatePlan(newPlan) -> validation
|
|
98
|
+
if (validation.valid):
|
|
99
|
+
plan = newPlan
|
|
100
|
+
return ChangeResult{success: true, plan: plan}
|
|
101
|
+
else:
|
|
102
|
+
return ChangeResult{success: false, error: validation.reason}
|
|
103
|
+
|
|
104
|
+
query GetPlan() -> (string):
|
|
105
|
+
return plan
|
|
106
|
+
|
|
107
|
+
plan = "free"
|
|
108
|
+
credits = 0
|
|
109
|
+
cancelled = false
|
|
110
|
+
|
|
111
|
+
for:
|
|
112
|
+
await one:
|
|
113
|
+
signal Cancel:
|
|
114
|
+
break
|
|
115
|
+
timer(30d):
|
|
116
|
+
activity BillUser(userId, plan)
|
|
117
|
+
|
|
118
|
+
close complete
|
|
119
|
+
|
|
120
|
+
# --- Shipping workflow: update racing in await one ---
|
|
121
|
+
# Shows update as an await one case competing with a timer.
|
|
122
|
+
|
|
123
|
+
workflow ShippingWorkflow(orderId: string) -> (ShipResult):
|
|
124
|
+
update ChangeAddress(newAddress: Address) -> (AddressResult):
|
|
125
|
+
address = newAddress
|
|
126
|
+
return AddressResult{updated: true}
|
|
127
|
+
|
|
128
|
+
query GetAddress() -> (Address):
|
|
129
|
+
return address
|
|
130
|
+
|
|
131
|
+
activity LoadOrderRecord(orderId) -> order
|
|
132
|
+
address = order.shippingAddress
|
|
133
|
+
|
|
134
|
+
# Race: accept address changes until the cutoff window closes
|
|
135
|
+
await one:
|
|
136
|
+
update ChangeAddress:
|
|
137
|
+
activity NotifyShippingUpdate(orderId, address)
|
|
138
|
+
timer(1h):
|
|
139
|
+
activity FinalizeShipping(orderId, address)
|
|
140
|
+
|
|
141
|
+
activity Ship(orderId, address) -> shipment
|
|
142
|
+
close complete(ShipResult{shipment: shipment})
|
|
143
|
+
|
|
144
|
+
# --- Update handler waiting on condition ---
|
|
145
|
+
# Shows the condition + update handler pattern where the caller blocks
|
|
146
|
+
# until the condition is set by the main workflow body.
|
|
147
|
+
|
|
148
|
+
workflow JobCoordinator(config: JobConfig):
|
|
149
|
+
state:
|
|
150
|
+
condition jobReady
|
|
151
|
+
|
|
152
|
+
signal Shutdown():
|
|
153
|
+
shutdownRequested = true
|
|
154
|
+
|
|
155
|
+
update WaitUntilReady() -> (JobState):
|
|
156
|
+
await jobReady
|
|
157
|
+
return JobState{ready: true}
|
|
158
|
+
|
|
159
|
+
query GetStatus() -> (string):
|
|
160
|
+
return status
|
|
161
|
+
|
|
162
|
+
status = "provisioning"
|
|
163
|
+
activity ProvisionJobRunner(config)
|
|
164
|
+
activity StartJobRunner(config)
|
|
165
|
+
set jobReady
|
|
166
|
+
status = "running"
|
|
167
|
+
|
|
168
|
+
await signal Shutdown
|
|
169
|
+
close complete
|
|
170
|
+
|
|
171
|
+
# --- Supporting activities ---
|
|
172
|
+
|
|
173
|
+
activity LoadOrderRecord(orderId: string) -> (Order):
|
|
174
|
+
return db.get(orderId)
|
|
175
|
+
|
|
176
|
+
activity ValidateOrderRecord(order: Order):
|
|
177
|
+
validate(order)
|
|
178
|
+
|
|
179
|
+
activity NotifyApprovers(request: Request):
|
|
180
|
+
notify_approvers(request)
|
|
181
|
+
|
|
182
|
+
activity NotifyExpired(request: Request):
|
|
183
|
+
notify_expired(request)
|
|
184
|
+
|
|
185
|
+
activity RunBatchProcessing(batchId: string, count: int) -> (BatchResult):
|
|
186
|
+
return process(batchId, count)
|
|
187
|
+
|
|
188
|
+
activity BillUser(userId: string, plan: string):
|
|
189
|
+
bill(userId, plan)
|
|
190
|
+
|
|
191
|
+
activity ValidatePlan(plan: string) -> (Validation):
|
|
192
|
+
return validate_plan(plan)
|
|
193
|
+
|
|
194
|
+
activity NotifyShippingUpdate(orderId: string, address: Address):
|
|
195
|
+
notify_shipping(orderId, address)
|
|
196
|
+
|
|
197
|
+
activity FinalizeShipping(orderId: string, address: Address):
|
|
198
|
+
finalize(orderId, address)
|
|
199
|
+
|
|
200
|
+
activity Ship(orderId: string, address: Address) -> (Shipment):
|
|
201
|
+
return ship(orderId, address)
|
|
202
|
+
|
|
203
|
+
activity ProvisionJobRunner(config: JobConfig):
|
|
204
|
+
provision(config)
|
|
205
|
+
|
|
206
|
+
activity StartJobRunner(config: JobConfig):
|
|
207
|
+
start(config)
|
|
208
|
+
|
|
209
|
+
# --- Worker and namespace ---
|
|
210
|
+
|
|
211
|
+
worker handlerWorker:
|
|
212
|
+
workflow OrderTrackingWorkflow
|
|
213
|
+
workflow ApprovalWorkflow
|
|
214
|
+
workflow BatchCollector
|
|
215
|
+
workflow SubscriptionWorkflow
|
|
216
|
+
workflow ShippingWorkflow
|
|
217
|
+
workflow JobCoordinator
|
|
218
|
+
activity LoadOrderRecord
|
|
219
|
+
activity ValidateOrderRecord
|
|
220
|
+
activity NotifyApprovers
|
|
221
|
+
activity NotifyExpired
|
|
222
|
+
activity RunBatchProcessing
|
|
223
|
+
activity BillUser
|
|
224
|
+
activity ValidatePlan
|
|
225
|
+
activity NotifyShippingUpdate
|
|
226
|
+
activity FinalizeShipping
|
|
227
|
+
activity Ship
|
|
228
|
+
activity ProvisionJobRunner
|
|
229
|
+
activity StartJobRunner
|
|
230
|
+
|
|
231
|
+
namespace handlers:
|
|
232
|
+
worker handlerWorker
|
|
233
|
+
options:
|
|
234
|
+
task_queue: "handlers"
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Workers, Task Queues, and Deployment Topology
|
|
2
|
+
|
|
3
|
+
> **Example:** [`task-queues.twf`](./task-queues.twf)
|
|
4
|
+
|
|
5
|
+
Workers group type registrations. Task queues route work to workers. Namespaces instantiate workers with deployment options. Together they answer: **what runs together, how work reaches it, and where it's deployed.**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Worker Type Sets
|
|
10
|
+
|
|
11
|
+
Workers are reusable type sets that list which workflows, activities, and nexus services belong together:
|
|
12
|
+
|
|
13
|
+
```twf
|
|
14
|
+
worker orderTypes:
|
|
15
|
+
workflow ProcessOrder
|
|
16
|
+
workflow CancelOrder
|
|
17
|
+
activity ChargePayment
|
|
18
|
+
activity SendNotification
|
|
19
|
+
|
|
20
|
+
worker paymentTypes:
|
|
21
|
+
activity ChargePayment
|
|
22
|
+
activity GetPaymentStatus
|
|
23
|
+
nexus service PaymentService
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Workers contain only type references — no deployment config. Naming: `lowerCamelCase`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Namespace Instantiation
|
|
31
|
+
|
|
32
|
+
Namespaces instantiate workers with deployment options (task queue, versioning strategy, and more — see [Worker Options](#worker-options) below) and expose nexus endpoints for external callers:
|
|
33
|
+
|
|
34
|
+
```twf
|
|
35
|
+
namespace ecommerce:
|
|
36
|
+
worker orderTypes
|
|
37
|
+
options:
|
|
38
|
+
task_queue: "orderProcessing"
|
|
39
|
+
max_concurrent_activity_executions: 50
|
|
40
|
+
worker paymentTypes
|
|
41
|
+
options:
|
|
42
|
+
task_queue: "payments"
|
|
43
|
+
# Nexus endpoint lives in the target namespace alongside the worker that serves it.
|
|
44
|
+
# External callers in other namespaces reach PaymentService via this endpoint.
|
|
45
|
+
nexus endpoint PaymentEndpoint
|
|
46
|
+
options:
|
|
47
|
+
task_queue: "payments"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The same worker type set can be reused across namespaces:
|
|
51
|
+
|
|
52
|
+
```twf
|
|
53
|
+
namespace staging:
|
|
54
|
+
worker orderTypes
|
|
55
|
+
options:
|
|
56
|
+
task_queue: "staging-orders"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Worker Options
|
|
60
|
+
|
|
61
|
+
The worker `options:` block is the **union of SDK worker options**, accepted *permissively* — the parser does no per-language validation, so an option a given SDK lacks is still allowed. Use it to express **strategy and intent at design altitude**, not exhaustive numeric ops tuning (exact poller counts and cache TTLs belong in implementation):
|
|
62
|
+
|
|
63
|
+
- **`task_queue`** (required) — routing; pins the worker pool.
|
|
64
|
+
- **`versioning: none | build_id | deployment`** — the worker-versioning *strategy* a pool follows (see [versioning.md](./versioning.md#declaring-the-strategy-in-twf)). A reliability/availability decision the design should make; concrete Build IDs / deployment names are deploy-time inputs, not `.twf` content.
|
|
65
|
+
- **`enable_sessions`** — a genuine design call: sessions pin a sequence of activities to one worker (host-affinity for stateful or resource-bound work), distinct from numeric tuning.
|
|
66
|
+
- **Concurrency caps and rate limiters** (`max_concurrent_*`, `*_rate_limit`, sticky cache) are part of the union but are ops tuning — include them only when a workload demands it; the design agent generally should not hand-set them.
|
|
67
|
+
|
|
68
|
+
The full key list lives in the spec — run `twf spec` or see [`tools/spec/sections/03-workers-and-namespaces.md`](../../../tools/spec/sections/03-workers-and-namespaces.md) — rather than being duplicated here (avoids drift, keeps the design at altitude).
|
|
69
|
+
|
|
70
|
+
### Nexus Endpoints in Namespaces
|
|
71
|
+
|
|
72
|
+
A `nexus endpoint` in a namespace declaration exposes a nexus service to callers in other namespaces. The endpoint's `task_queue` must match the queue where a worker with that nexus service is running. See [nexus.md](./nexus.md) for the full cross-namespace pattern.
|
|
73
|
+
|
|
74
|
+
### What the Resolver Validates
|
|
75
|
+
|
|
76
|
+
- **Undefined references** — Catch typos (e.g., referencing a workflow or worker that doesn't exist)
|
|
77
|
+
- **Coverage gaps** — Warn when a defined workflow/activity isn't registered on any instantiated worker
|
|
78
|
+
- **Task queue coherence** — Error when different workers on the same queue register different type sets
|
|
79
|
+
- **Missing configuration** — Error when a worker instantiation is missing the required `task_queue` option
|
|
80
|
+
|
|
81
|
+
### Rules
|
|
82
|
+
|
|
83
|
+
- Workers contain only `workflow`, `activity`, and `nexus service` entries (type set only, no deployment config)
|
|
84
|
+
- Each worker instantiation in a namespace requires a `task_queue` option
|
|
85
|
+
- Worker names use lowerCamelCase; workflow/activity names keep UpperCamelCase
|
|
86
|
+
- Multiple workers can be instantiated on the same task queue (but must register the same type sets)
|
|
87
|
+
- Workers not instantiated in any namespace produce warnings
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Task Queue Design Decisions
|
|
92
|
+
|
|
93
|
+
### Single vs Multiple Task Queues
|
|
94
|
+
|
|
95
|
+
| Single Queue | Multiple Queues |
|
|
96
|
+
|--------------|-----------------|
|
|
97
|
+
| Simple deployment | More operational complexity |
|
|
98
|
+
| All workers handle all work | Workers specialize |
|
|
99
|
+
| Scaling affects everything | Scale queues independently |
|
|
100
|
+
| One failure domain | Isolated failure domains |
|
|
101
|
+
|
|
102
|
+
### When to Use Separate Task Queues
|
|
103
|
+
|
|
104
|
+
> **Different runtimes do not require different namespaces — use task queues.** GPU workers, licensed-software workers, region-specific workers, and bursty-vs-steady workloads are all *task queue* concerns within a single namespace. Reaching for a namespace per runtime is the misconception that produces namespace-per-worker. See [namespaces.md](../reference/namespaces.md) — the default namespace count is one.
|
|
105
|
+
|
|
106
|
+
| Use Case | Rationale |
|
|
107
|
+
|----------|-----------|
|
|
108
|
+
| **Different resource requirements** | CPU-heavy vs I/O-heavy work |
|
|
109
|
+
| **Different scaling characteristics** | Bursty vs steady workloads |
|
|
110
|
+
| **Isolation requirements** | Tenant isolation, security boundaries |
|
|
111
|
+
| **Priority handling** | High-priority vs batch processing |
|
|
112
|
+
| **Geographic distribution** | Region-specific workers |
|
|
113
|
+
| **Specialized capabilities** | GPU workers, licensed software |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Task Queue Patterns
|
|
118
|
+
|
|
119
|
+
### Priority Queues
|
|
120
|
+
|
|
121
|
+
Separate queues for different priorities:
|
|
122
|
+
|
|
123
|
+
```twf
|
|
124
|
+
workflow OrderWorkflow(order: Order) -> (Result):
|
|
125
|
+
if (order.priority == "express"):
|
|
126
|
+
activity ProcessOrder(order)
|
|
127
|
+
options:
|
|
128
|
+
task_queue: "high-priority"
|
|
129
|
+
else:
|
|
130
|
+
activity ProcessOrder(order)
|
|
131
|
+
options:
|
|
132
|
+
task_queue: "standard"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Tenant Isolation
|
|
136
|
+
|
|
137
|
+
Separate queues per tenant:
|
|
138
|
+
|
|
139
|
+
```twf
|
|
140
|
+
workflow TenantWorkflow(tenantId: string, data: Data) -> (Result):
|
|
141
|
+
# Route to tenant-specific queue
|
|
142
|
+
activity ProcessData(data)
|
|
143
|
+
options:
|
|
144
|
+
task_queue: "tenant-{tenantId}"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Capability-Based Routing
|
|
148
|
+
|
|
149
|
+
Route based on required capabilities:
|
|
150
|
+
|
|
151
|
+
```twf
|
|
152
|
+
workflow MediaWorkflow(media: Media) -> (Result):
|
|
153
|
+
if (media.type == "video"):
|
|
154
|
+
# Needs GPU workers
|
|
155
|
+
activity TranscodeVideo(media)
|
|
156
|
+
options:
|
|
157
|
+
task_queue: "gpu-workers"
|
|
158
|
+
else:
|
|
159
|
+
activity ProcessImage(media)
|
|
160
|
+
options:
|
|
161
|
+
task_queue: "standard-workers"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Geographic Routing
|
|
165
|
+
|
|
166
|
+
Route to region-specific workers:
|
|
167
|
+
|
|
168
|
+
```twf
|
|
169
|
+
workflow GlobalWorkflow(request: Request) -> (Result):
|
|
170
|
+
# Route to nearest region
|
|
171
|
+
activity ProcessLocally(request)
|
|
172
|
+
options:
|
|
173
|
+
task_queue: "workers-{request.region}"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Anti-Patterns
|
|
179
|
+
|
|
180
|
+
### One Queue Per Workflow Type
|
|
181
|
+
|
|
182
|
+
```twf
|
|
183
|
+
# BAD: Unnecessary complexity — one queue per type
|
|
184
|
+
# Results in many queues, complex deployment
|
|
185
|
+
|
|
186
|
+
# GOOD: Shared queue unless isolation needed
|
|
187
|
+
# Put related workflows on the same worker and task queue
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Dynamic Queue Names Without Cleanup
|
|
191
|
+
|
|
192
|
+
```twf
|
|
193
|
+
# BAD: Creates queue per request (never cleaned up)
|
|
194
|
+
workflow Process(requestId: string):
|
|
195
|
+
activity DoWork()
|
|
196
|
+
options:
|
|
197
|
+
task_queue: "request-{requestId}" # Unbounded queues!
|
|
198
|
+
|
|
199
|
+
# GOOD: Bounded set of queues
|
|
200
|
+
workflow Process(request: Request):
|
|
201
|
+
queue = selectQueue(request.priority) # "high", "medium", "low"
|
|
202
|
+
activity DoWork()
|
|
203
|
+
options:
|
|
204
|
+
task_queue: queue
|
|
205
|
+
```
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Workers, task queues, and deployment topology
|
|
2
|
+
# Demonstrates: worker type sets, namespace instantiation, explicit routing, capability-based queues
|
|
3
|
+
|
|
4
|
+
# --- Explicit routing + implicit inheritance ---
|
|
5
|
+
|
|
6
|
+
workflow OrderWorkflow(order: Order) -> (Result):
|
|
7
|
+
# Implicit: inherits OrderWorkflow's queue ("orders")
|
|
8
|
+
activity ValidateOrder(order) -> validated
|
|
9
|
+
|
|
10
|
+
# Explicit: route to dedicated payment workers
|
|
11
|
+
activity ChargePayment(order) -> payment
|
|
12
|
+
options:
|
|
13
|
+
task_queue: "payments"
|
|
14
|
+
|
|
15
|
+
# Explicit: conditional routing by shipping priority
|
|
16
|
+
if (order.priority == "express"):
|
|
17
|
+
activity ShipOrder(order, payment) -> shipment
|
|
18
|
+
options:
|
|
19
|
+
task_queue: "shipping-express"
|
|
20
|
+
else:
|
|
21
|
+
activity ShipOrder(order, payment) -> shipment
|
|
22
|
+
options:
|
|
23
|
+
task_queue: "shipping-standard"
|
|
24
|
+
|
|
25
|
+
close complete(Result{shipment})
|
|
26
|
+
|
|
27
|
+
# --- Capability-based routing ---
|
|
28
|
+
|
|
29
|
+
workflow MediaPipeline(media: Media) -> (Result):
|
|
30
|
+
# Explicit: route to GPU-capable workers
|
|
31
|
+
activity TranscodeVideo(media) -> transcoded
|
|
32
|
+
options:
|
|
33
|
+
task_queue: "gpu-transcode"
|
|
34
|
+
start_to_close_timeout: 10m
|
|
35
|
+
|
|
36
|
+
# Implicit: inherits MediaPipeline's queue ("media")
|
|
37
|
+
activity GenerateThumbnail(transcoded) -> thumb
|
|
38
|
+
|
|
39
|
+
close complete(Result{thumb})
|
|
40
|
+
|
|
41
|
+
# --- Activities ---
|
|
42
|
+
|
|
43
|
+
activity ValidateOrder(order: Order) -> (ValidationResult):
|
|
44
|
+
return validate(order)
|
|
45
|
+
|
|
46
|
+
activity ChargePayment(order: Order) -> (Payment):
|
|
47
|
+
return charge(order)
|
|
48
|
+
|
|
49
|
+
activity ShipOrder(order: Order, payment: Payment) -> (Shipment):
|
|
50
|
+
return ship(order)
|
|
51
|
+
|
|
52
|
+
activity TranscodeVideo(media: Media) -> (TranscodeResult):
|
|
53
|
+
heartbeat(media.progress)
|
|
54
|
+
return transcode(media)
|
|
55
|
+
|
|
56
|
+
activity GenerateThumbnail(video: Video) -> (Thumbnail):
|
|
57
|
+
return thumbnail(video)
|
|
58
|
+
|
|
59
|
+
# --- Nexus services (exposed for external callers in other namespaces) ---
|
|
60
|
+
|
|
61
|
+
# PaymentService: registered on paymentWorker, exposed via PaymentEndpoint in ecommerce namespace.
|
|
62
|
+
# External callers (e.g., partner namespace below) reach it cross-namespace via the endpoint.
|
|
63
|
+
nexus service PaymentService:
|
|
64
|
+
sync CheckPaymentStatus(paymentId: string) -> (PaymentStatus):
|
|
65
|
+
activity GetPaymentStatus(paymentId) -> status
|
|
66
|
+
close complete(status)
|
|
67
|
+
|
|
68
|
+
# AnalyticsService: independent scaling on its own worker, same cross-namespace exposure pattern.
|
|
69
|
+
nexus service AnalyticsService:
|
|
70
|
+
async TrackEvent workflow TrackEventWorkflow
|
|
71
|
+
|
|
72
|
+
activity GetPaymentStatus(paymentId: string) -> (PaymentStatus):
|
|
73
|
+
return lookup(paymentId)
|
|
74
|
+
|
|
75
|
+
workflow TrackEventWorkflow(event: Event):
|
|
76
|
+
activity RecordEvent(event)
|
|
77
|
+
close complete
|
|
78
|
+
|
|
79
|
+
activity RecordEvent(event: Event):
|
|
80
|
+
record(event)
|
|
81
|
+
|
|
82
|
+
# --- Workers ---
|
|
83
|
+
|
|
84
|
+
worker orderWorker:
|
|
85
|
+
workflow OrderWorkflow
|
|
86
|
+
activity ValidateOrder
|
|
87
|
+
|
|
88
|
+
worker paymentWorker:
|
|
89
|
+
activity ChargePayment
|
|
90
|
+
activity GetPaymentStatus
|
|
91
|
+
nexus service PaymentService
|
|
92
|
+
|
|
93
|
+
worker analyticsWorker:
|
|
94
|
+
workflow TrackEventWorkflow
|
|
95
|
+
activity RecordEvent
|
|
96
|
+
nexus service AnalyticsService
|
|
97
|
+
|
|
98
|
+
worker expressShipWorker:
|
|
99
|
+
activity ShipOrder
|
|
100
|
+
|
|
101
|
+
worker standardShipWorker:
|
|
102
|
+
activity ShipOrder
|
|
103
|
+
|
|
104
|
+
# Tiered registration: a workflow or nexus service hosted on multiple
|
|
105
|
+
# workers, each on its OWN task queue (same pattern as expressShipWorker
|
|
106
|
+
# / standardShipWorker above). The registered type set is shared across
|
|
107
|
+
# the tier so the caller picks behaviour by routing to the right queue —
|
|
108
|
+
# realtime vs batch analytics here, primary vs DR-region payments below.
|
|
109
|
+
worker realtimeAnalyticsWorker:
|
|
110
|
+
workflow TrackEventWorkflow
|
|
111
|
+
activity RecordEvent
|
|
112
|
+
|
|
113
|
+
worker paymentWorkerSecondary:
|
|
114
|
+
activity ChargePayment
|
|
115
|
+
activity GetPaymentStatus
|
|
116
|
+
nexus service PaymentService
|
|
117
|
+
|
|
118
|
+
worker mediaWorker:
|
|
119
|
+
workflow MediaPipeline
|
|
120
|
+
activity GenerateThumbnail
|
|
121
|
+
|
|
122
|
+
worker gpuWorker:
|
|
123
|
+
activity TranscodeVideo
|
|
124
|
+
|
|
125
|
+
# --- Namespaces ---
|
|
126
|
+
|
|
127
|
+
namespace ecommerce:
|
|
128
|
+
worker orderWorker
|
|
129
|
+
options:
|
|
130
|
+
task_queue: "orders"
|
|
131
|
+
worker paymentWorker
|
|
132
|
+
options:
|
|
133
|
+
task_queue: "payments"
|
|
134
|
+
# Same registered type set as paymentWorker, on its own queue —
|
|
135
|
+
# callers route by region by picking the destination task queue.
|
|
136
|
+
worker paymentWorkerSecondary
|
|
137
|
+
options:
|
|
138
|
+
task_queue: "payments-secondary"
|
|
139
|
+
worker expressShipWorker
|
|
140
|
+
options:
|
|
141
|
+
task_queue: "shipping-express"
|
|
142
|
+
worker standardShipWorker
|
|
143
|
+
options:
|
|
144
|
+
task_queue: "shipping-standard"
|
|
145
|
+
# Endpoints live in the target namespace (ecommerce) alongside the workers that serve them.
|
|
146
|
+
# External callers in other namespaces (e.g., partner) reach these services cross-namespace.
|
|
147
|
+
nexus endpoint PaymentEndpoint
|
|
148
|
+
options:
|
|
149
|
+
task_queue: "payments"
|
|
150
|
+
worker analyticsWorker
|
|
151
|
+
options:
|
|
152
|
+
task_queue: "analytics"
|
|
153
|
+
# Same registered type set as analyticsWorker, on its own queue —
|
|
154
|
+
# callers pick the realtime tier by dispatching to this queue.
|
|
155
|
+
worker realtimeAnalyticsWorker
|
|
156
|
+
options:
|
|
157
|
+
task_queue: "analytics-realtime"
|
|
158
|
+
nexus endpoint AnalyticsEndpoint
|
|
159
|
+
options:
|
|
160
|
+
task_queue: "analytics"
|
|
161
|
+
|
|
162
|
+
namespace media:
|
|
163
|
+
worker mediaWorker
|
|
164
|
+
options:
|
|
165
|
+
task_queue: "media"
|
|
166
|
+
worker gpuWorker
|
|
167
|
+
options:
|
|
168
|
+
task_queue: "gpu-transcode"
|
|
169
|
+
|
|
170
|
+
# --- Cross-namespace caller ---
|
|
171
|
+
# A partner service in a separate namespace reaches into ecommerce via Nexus endpoints.
|
|
172
|
+
|
|
173
|
+
workflow PartnerCheckout(request: PartnerRequest) -> (PartnerResult):
|
|
174
|
+
nexus PaymentEndpoint PaymentService.CheckPaymentStatus(request.paymentId) -> status
|
|
175
|
+
detach nexus AnalyticsEndpoint AnalyticsService.TrackEvent(request.event)
|
|
176
|
+
close complete(PartnerResult{status: status})
|
|
177
|
+
|
|
178
|
+
worker partnerWorker:
|
|
179
|
+
workflow PartnerCheckout
|
|
180
|
+
|
|
181
|
+
namespace partner:
|
|
182
|
+
worker partnerWorker
|
|
183
|
+
options:
|
|
184
|
+
task_queue: "partner"
|