Getting Started
This page builds a small workflow and runs it on the local World, which stores runs as JSON files and needs no database or worker.
Install
go get github.com/eriicafes/go-workflowgo-workflow requires Go 1.26 or later.
1. Define a workflow
A workflow is a struct type that embeds workflow.Struct[Out] to declare its output Out. Keep workflows in a workflows package and their runners in a flows package inside it, so request handlers can start workflows without importing their runners.
workflows/
reports.go workflow types
flows/
generate_report.go runners and their steps// workflows/reports.go
package workflows
import "github.com/eriicafes/go-workflow"
type GenerateReport struct {
workflow.Struct[GenerateReportResult]
Topic string
}
type GenerateReportResult struct {
Summary string
}The workflow name is the package path and type name, for example example.com/app/workflows.GenerateReport.
2. Write a runner and its steps
A runner executes the workflow. It is a plain function that takes the workflow as input and calls steps, which do the real work. Keep each runner in a file with the steps it calls, in workflows/flows.
// workflows/flows/generate_report.go
package flows
import (
"context"
"strings"
"example.com/app/workflows"
"github.com/eriicafes/go-workflow"
)
func GenerateReport(ctx context.Context, input workflows.GenerateReport) (workflows.GenerateReportResult, error) {
sources, err := workflow.Step(ctx, findSources, input.Topic)
if err != nil {
return workflows.GenerateReportResult{}, err
}
return workflows.GenerateReportResult{Summary: strings.Join(sources, ", ")}, nil
}
func findSources(ctx context.Context, topic string) ([]string, error) {
return []string{"source for " + topic}, nil
}3. Register runners
workflow.Use pairs a runner with its workflow, and worlds.Runners collects them into a set the runtime executes.
runners := worlds.Runners(
workflow.Use(flows.GenerateReport),
)4. Open a World and start a run
package main
import (
"context"
"fmt"
"log"
"github.com/eriicafes/go-workflow"
"github.com/eriicafes/go-workflow/worlds"
localworld "github.com/eriicafes/go-workflow/worlds/local"
"example.com/app/workflows"
"example.com/app/workflows/flows"
)
func main() {
ctx := context.Background()
runners := worlds.Runners(workflow.Use(flows.GenerateReport))
world, err := localworld.Open(localworld.Config{Dir: ".workflows"}, runners)
if err != nil {
log.Fatal(err)
}
defer world.Close()
if err := world.Prepare(ctx); err != nil {
log.Fatal(err)
}
ctx = workflow.NewContext(ctx, workflow.NewClient(world))
run, err := workflow.Start(ctx, workflows.GenerateReport{Topic: "workflows"})
if err != nil {
log.Fatal(err)
}
result, err := run.Value(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Summary)
}workflow.Start needs only the workflow type and a Client in the context. It returns a Run handle without waiting, and Value blocks until the run finishes.
5. Check your runners
The workflow analyzer reports code in runners that would break replay, such as goroutines, time.Now, or I/O outside steps.
The easiest way to run it is with go-dev, which checks your runners before every start or reload of your app. Add workflowdev.Check as a Prepare hook:
// cmd/dev/main.go
package main
import (
"github.com/eriicafes/go-dev"
"github.com/eriicafes/go-workflow/workflowdev"
)
func main() {
session := dev.New()
err := session.Run(dev.Cmd{
Run: dev.Package("./cmd/app"),
Prepare: dev.Hooks(workflowdev.Check("./...")),
Watch: dev.Values("cmd", "workflows"),
})
session.Catch(err)
session.Catch(session.Wait())
}go run ./cmd/devDevelopment adds the Workflow SDK web UI to the same setup.
Without go-dev, for example in CI, install the analyzer and run it as a vet tool:
go install github.com/eriicafes/go-workflow/cmd/workflowcheck@latest
go vet -vettool=$(which workflowcheck) ./...Next steps
- Learn how replay works and why runners must be deterministic.
- Add retries and error handling to steps.
- Run steps concurrently, sleep, or wait for hooks.
- Set up the development loop with go-dev, the analyzer, and the web UI.
- Split your application and worker and pick a production World.
- Browse
examples/basic, a full application that uses the local World in development and has a worker for each production World.