Temporal Workflows with Go: a roadmap
Why this matters, in 30 seconds
Real business processes are long and fragile. A signup charges a card, sends emails, waits 14 days for a trial to end, then converts the account. An order waits for a courier, a payment, a human. Without Temporal you glue this together with cron jobs, queues, status columns, retry loops and "stuck order" dashboards, and every crash leaves something half done.
A Workflow is ordinary Go code that describes the whole process, including the waiting. Temporal records every step in an Event History. If a Worker crashes, another Worker replays the code against that history and continues exactly where it stopped, even if the stop happened 13 days into a 14-day sleep. You write the process as a function. Temporal makes it durable.
The price: Workflow code must be deterministic, and you must change it carefully once executions are running. Most of this roadmap is about using that power without breaking that rule.
The roadmap
Read in order. Each stage builds on the previous one.
Stage 1: Foundations (core concepts, must-know)
| # | Chapter | You'll be able to… |
|---|---|---|
| 01 | What is a Workflow? | Explain the problem Workflows solve, Definition vs Type vs Execution, the Workflow/Activity split and the vocabulary. Run a first end-to-end program. |
| 02 | Defining Workflows in Go | Write Workflow functions with the right signature, inputs and results, name and register them, and use the Go replacements for time, go, chan, select and maps. |
Stage 2: How Workflows really run (core concepts, the mental model)
| # | Chapter | You'll be able to… |
|---|---|---|
| 03 | Determinism, Event History & Replay | Read an Event History, explain Commands vs Events and replay, spot non-deterministic code, use Side Effects, and write a replay test. |
| 04 | Workflow Execution: lifecycle, IDs & limits | Pick Workflow IDs, use the ID Reuse and Conflict policies, read statuses, set timeouts carefully, and know the history limits. |
| 05 | Timers, Selectors & concurrency | Sleep for days, race a Timer against an Activity, fan out and pick the first result, and wait on conditions with workflow.Await. |
Stage 3: Talking to and composing Workflows (daily use)
| # | Chapter | You'll be able to… |
|---|---|---|
| 06 | Message passing: Queries, Signals & Updates | Turn a Workflow into a stateful service: read it with Queries, steer it with Signals and Updates, use Signal-With-Start and Update-With-Start, and write safe handlers. |
| 07 | Child Workflows | Split big processes into Child Workflows, choose a Parent Close Policy, and start fire-and-forget children correctly. |
Stage 4: Controlling executions over time (operations & long-running)
| # | Chapter | You'll be able to… |
|---|---|---|
| 08 | Cancel, Terminate, Pause & Reset | Choose between the four controls, write cancellation-aware Workflows that clean up, and know what each control guarantees. |
| 09 | Continue-As-New | Run entity Workflows forever by checkpointing state into a fresh run before the history gets too big. |
| 10 | Schedules, Start Delay & Cron Jobs | Run Workflows on a clock with Schedules (overlap, catch-up, backfill, pause), delay a one-off start, and recognise legacy Cron Jobs. |
Stage 5: Evolving and extending (advanced)
| # | Chapter | You'll be able to… |
|---|---|---|
| 11 | Versioning: Worker Versioning & Patching | Ship changes to Workflow code without breaking running executions, with workflow.GetVersion or Worker Versioning. |
| 12 | Dynamic Workflows | Handle Workflow Types that aren't known at compile time. |
| 13 | Workflow Streams (Public Preview) | Stream progress events and LLM tokens from a Workflow to UIs in real time. |
Core concepts vs. implementation details
| Core concepts: understand these first | Implementation details, options & sub-features: learn when needed |
|---|---|
| Workflow Definition, Type and Execution (01) | Custom Workflow Type names, starting by name (02) |
| Workflow vs Activity: orchestration vs side effects (01) | workflow.Go, channels, WaitGroup, DeterministicKeys (02) |
| Event History, Commands, Events, Replay (03) | SideEffect, MutableSideEffect, IsReplaying, replay tests (03) |
| Determinism (03) | Go runtime non-determinism checks (03, 11) |
| Workflow ID vs Run ID, statuses (04) | ID Reuse / Conflict policies, Memo, Workflow timeouts & retries, limits (04) |
| Durable Timers (05) | Selectors, soft timeouts, pick-first, AwaitWithTimeout (05) |
| Messages: Query, Signal, Update (06) | Validators, Signal/Update-With-Start, Mutex, AllHandlersFinished (06) |
| Cancellation vs Termination (08) | Disconnected cleanup contexts, Pause, Reset (08) |
| Continue-As-New and the Execution Chain (09) | Suggested-CAN reasons, draining Signals, test hooks (09) |
| Versioning strategy (11) | GetVersion lifecycle, cutover, Worker Versioning config (11) |
| — | Child Workflows (07), Schedules & Cron (10), Dynamic Workflows (12), Workflow Streams (13) |
Which tool do I need?
I need to…
├── run a multi-step process that must survive crashes → a Workflow → 01, 02
├── call an API / DB / LLM from it → an Activity → Activities guide
├── wait (minutes … years) → workflow.Sleep / Timer → 05
├── wait for whichever happens first (event, timeout, result) → Selector / AwaitWithTimeout → 05
├── read its state from outside → Query → 06
├── push an event into it, fire-and-forget → Signal → 06
├── change it and get an answer / a rejection back → Update (+ validator) → 06
├── create-or-reach "the one execution for this business key" → Workflow ID + Signal/Update-With-Start → 04, 06
├── split a huge process or delegate to another team's service → Child Workflow → 07
├── stop it → Cancel (cleanup) or Terminate (force) → 08
├── hold it during an incident → Pause (Pre-release) → 08
├── keep it alive forever (an entity) → Continue-As-New → 09
├── start it every day / hour / Monday → Schedule → 10
├── start it once, later → Start Delay → 10
├── change its code while executions are running → Worker Versioning or GetVersion → 11
└── show live progress in a UI → Workflow Streams (Preview) or Queries → 13, 06Go API cheat sheet
| I want to… | Go API | Chapter |
|---|---|---|
| Define a Workflow | func ProcessThing(ctx workflow.Context, in ThingInput) (ThingResult, error) | 02 |
| Register it | w.RegisterWorkflow(fn), w.RegisterWorkflowWithOptions(fn, workflow.RegisterOptions{Name: …}) | 02 |
| Start it | c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{ID, TaskQueue, …}, fn, in) → run.Get(ctx, &out) | 01, 04 |
| Get back to it | c.GetWorkflow(ctx, id, runID), c.DescribeWorkflow(ctx, id, runID) | 04 |
| Read time, log, sleep | workflow.Now(ctx), workflow.GetLogger(ctx), workflow.Sleep(ctx, d) | 02, 05 |
| Run things concurrently | workflow.Go, workflow.NewChannel, workflow.NewWaitGroup, workflow.NewSelector | 02, 05 |
| Iterate a map | for _, k := range workflow.DeterministicKeys(m) | 02 |
| Record a non-deterministic value | workflow.SideEffect, workflow.MutableSideEffect | 03 |
| Test replay safety | worker.NewWorkflowReplayer() + ReplayWorkflowHistoryFromJSONFile | 03, 11 |
| Control duplicates | WorkflowIDReusePolicy, WorkflowIDConflictPolicy, WorkflowExecutionErrorWhenAlreadyStarted | 04 |
| Durable timer / race | workflow.NewTimer, selector.AddFuture, workflow.AwaitWithTimeout | 05 |
| Handle messages | workflow.SetQueryHandler, workflow.GetSignalChannel, workflow.SetUpdateHandlerWithOptions | 06 |
| Send messages | c.QueryWorkflow, c.SignalWorkflow, c.SignalWithStartWorkflow, c.UpdateWorkflow, c.UpdateWithStartWorkflow | 06 |
| Wait for handlers | workflow.Await(ctx, func() bool { return workflow.AllHandlersFinished(ctx) }) | 06, 09 |
| Start a child | workflow.ExecuteChildWorkflow(workflow.WithChildOptions(ctx, opts), fn, in) | 07 |
| Clean up on cancel | defer + workflow.NewDisconnectedContext(ctx) | 08 |
| Cancel / terminate | c.CancelWorkflow, c.TerminateWorkflow (and …WithOptions for a reason) | 08 |
| Checkpoint into a new run | return workflow.NewContinueAsNewError(ctx, fn, nextInput), GetInfo(ctx).GetContinueAsNewSuggested() | 09 |
| Run on a schedule | c.ScheduleClient().Create(ctx, client.ScheduleOptions{ID, Spec, Action, …}) | 10 |
| Start later, once | client.StartWorkflowOptions{StartDelay: d} | 10 |
| Patch running code | workflow.GetVersion(ctx, "change-id", workflow.DefaultVersion, 1) | 11 |
| Catch unknown Workflow Types | w.RegisterDynamicWorkflow(fn, workflow.DynamicRegisterOptions{}) | 12 |
| Stream progress | workflowstreams.NewWorkflowStream, Topic(…).Publish, NewClient(…).Subscribe | 13 |
About the code samples
- Each chapter is a self-contained module named
github.com/acme/shop. Every Go block starts with// file: <path>so you can paste it into that path. Short ❌ fragments without that header are anti-patterns shown on purpose and aren't meant to compile. - All samples were compiled and
go vet-checked against Go SDK v1.50.0 (go.temporal.io/sdk) and, for chapter 13,go.temporal.io/sdk/contrib/workflowstreamsv0.1.1. - External systems (payment providers, couriers, email, LLMs) are interfaces or fakes. Swap in your real clients.
- Run everything locally with
temporal server start-dev(UI at http://localhost:8233).
Notes from checking the docs against the SDK
- Already-started errors. The Encyclopedia says the default Conflict Policy (Fail) returns a "Workflow execution already started" error. The Go client in SDK v1.50.0 doesn't: unless you set
WorkflowExecutionErrorWhenAlreadyStarted: true,ExecuteWorkflowreturns aWorkflowRunfor the existing run with anilerror. This also happens when the Reuse Policy rejects the start. See chapter 04. workflow.RequestCancelWorkflow()appears in the Go Versioning page's runtime-check list. It doesn't exist. The real function isworkflow.RequestCancelExternalWorkflow()(03, 11).- Dynamic handlers. The Dynamic Handler page lists Go among the SDKs with dynamic Workflows, Activities, Signals and Queries. SDK v1.50.0 has dynamic Workflows and Activities only. It has no dynamic Signal, Query or Update handler API (12).
- Pausing from Go. Workflow Pause is Pre-release. The Go client has no
Pausemethod, but the gRPCPauseWorkflowExecution/UnpauseWorkflowExecutioncalls exist throughc.WorkflowService()(08). GetVersionandpatched(). The Encyclopedia'spatched()walkthrough targets Python, .NET and Ruby. Go'sGetVersionbehaves the same way on replay without a marker: it keeps returningDefaultVersionfor that change ID (11).- Not in the Go docs pages, but in the SDK:
GetInfo(ctx).GetContinueAsNewSuggestedReasons()tells you why Continue-As-New is suggested.NewContinueAsNewErrorWithOptionsexists.DynamicRegisterOptions.LoadDynamicRuntimeOptionsexists.workflow.DeterministicKeysgives you sorted map keys.
Sources
Encyclopedia: Workflows · Workflow Definition · Workflow Execution · Workflow Id and Run Id · Events and Event History · Continue-As-New · Limits · Timers and Start Delay · Cancellation and Termination · Workflow Pause · Dynamic Handler · Schedule · Cron Job · Patching
Go SDK: Basics · Child Workflows · Continue-As-New · Cancellation · Timeouts · Message passing · Schedules · Timers · Selectors · Side Effects · Dynamic Workflow · Versioning · Workflow Streams