### Run Benchmark Utility Source: https://github.com/cschleiden/go-workflows/blob/main/bench/README.md Execute the benchmark utility from the command line. No specific setup is required beyond having the Go toolchain installed. ```shell go run . ``` -------------------------------- ### Orchestrate Workflow Execution Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/index.html.md This example demonstrates setting up the backend, starting a worker, creating a client, and initiating a workflow instance. Workflows can be managed from separate processes. ```go func main() { ctx := context.Background() b := sqlite.NewSqliteBackend("simple.sqlite") go runWorker(ctx, b) c := client.New(b) wf, err := c.CreateWorkflowInstance(ctx, client.WorkflowInstanceOptions{ InstanceID: uuid.NewString(), }, Workflow1, "input-for-workflow") if err != nil { panic("could not start workflow") } c2 := make(chan os.Signal, 1) signal.Notify(c2, os.Interrupt) <- c2 } ``` -------------------------------- ### Start Documentation Development Server Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Navigate to the 'docs' directory and execute the 'serve.sh' script to start a local documentation server with live reloading. This requires Docker. ```bash cd docs ./serve.sh ``` -------------------------------- ### Start a New Workflow Instance Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Use `CreateWorkflowInstance` on a client to start a new workflow. Pass options, the workflow to run, and any necessary inputs. ```go wf, err := c.CreateWorkflowInstance(ctx, client.WorkflowInstanceOptions{ InstanceID: uuid.NewString(), }, Workflow1, "input-for-workflow") if err != nil { // ... } ``` -------------------------------- ### Start Development Services with Docker Compose Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Use this command to start the necessary development services like MySQL and Redis. Ensure Docker is installed and running. ```bash docker compose up -d ``` -------------------------------- ### Install and Run golangci-lint Manually Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Instructions for installing golangci-lint v2.4.0 and running the linter manually with the project's configuration. Note that v1.x is not compatible. ```bash # Install golangci-lint v2 (required for this project) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0 # Run the full linter configuration - Takes ~12-15 seconds. golangci-lint run --timeout=5m ``` -------------------------------- ### Bootstrap and Build go-workflows Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Execute these commands to set up a working development environment by downloading dependencies, building all packages, and starting development services. ```bash # 1. Download Go dependencies - Takes ~3.5 minutes. Allow sufficient timeout. go mod download # 2. Build all packages - Takes ~40 seconds. go build -v ./... # 3. Start development dependencies - Takes ~13 seconds. docker compose up -d ``` -------------------------------- ### Install golang-migrate for Sqlite Source: https://github.com/cschleiden/go-workflows/blob/main/backend/sqlite/README.md Install the golang-migrate CLI with support for SQLite and MySQL databases. This command ensures you have the necessary tools for database migration management. ```bash go install -tags 'sqlite3','mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest ``` -------------------------------- ### Workflow Version 1 Example Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_faq.md Illustrates the initial version of a workflow before introducing conditional logic for new activities. ```go func Workflow1(ctx workflow.Context) { r1, _ := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, 35, 12).Get(ctx) log.Println("A1 result:", r1) r2, _ := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity2).Get(ctx) log.Println("A2 result:", r2) } ``` -------------------------------- ### Lint Project Source: https://github.com/cschleiden/go-workflows/blob/main/DEVELOPMENT.md Run this command to lint the project code. Requires golangci-lint to be installed. ```bash make lint ``` -------------------------------- ### Set up a Go Worker Source: https://github.com/cschleiden/go-workflows/blob/main/README.md Register workflows and activities with the worker for execution. Ensure the worker starts correctly. ```go func runWorker(ctx context.Context, mb backend.Backend) { w := worker.New(mb, nil) w.RegisterWorkflow(Workflow1) w.RegisterActivity(Activity1) w.RegisterActivity(Activity2) if err := w.Start(ctx); err != nil { panic("could not start worker") } } ``` -------------------------------- ### Run Simple Workflow Sample Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Execute the simple workflow example. By default, it uses the Redis backend. Specify '-backend sqlite' to use the SQLite backend. ```bash # Simple workflow example with Redis backend (default) cd samples/simple && go run . # Simple workflow with SQLite backend cd samples/simple && go run . -backend sqlite ``` -------------------------------- ### Run Benchmark and Web Samples Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Commands to run the benchmark utility and the web example, which includes a diagnostic UI accessible at http://localhost:3000/diag. ```bash # Benchmark utility cd bench && go run . # Web example with diagnostic UI cd samples/web && go run . # Access UI at http://localhost:3000/diag ``` -------------------------------- ### Create PostgreSQL Migration Source: https://github.com/cschleiden/go-workflows/blob/main/backend/postgres/README.md Use this command to create a new SQL migration file. Ensure golang-migrate/migrate is installed. ```bash migrate create -ext sql -dir ./db/migrations -seq ``` -------------------------------- ### Workflow Version 2 Example Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_faq.md Shows a modified workflow that includes an additional activity, Activity3, intended for newer workflow versions. ```go func Workflow1(ctx workflow.Context) { var r1 int r1, _ := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, 35, 12).Get(ctx) log.Println("A1 result:", r1) r3, _ := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity3).Get(ctx) log.Println("A3 result:", r3) r2, _ := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity2).Get(ctx) log.Println("A2 result:", r2) } ``` -------------------------------- ### Execute a Sub-Workflow in Go Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Use `workflow.CreateSubWorkflowInstance` to start a sub-workflow. The returned Future resolves when the sub-workflow completes. ```go func Workflow1(ctx workflow.Context, msg string) error { result, err := workflow.CreateSubWorkflowInstance[int]( ctx, workflow.SubWorkflowInstanceOptions{}, SubWorkflow, "some input").Get(ctx) if err != nil { return errors.Wrap(err, "could not get sub workflow result") } logger.Debug("Sub workflow result:", "result", result) return nil } func SubWorkflow(ctx workflow.Context, msg string) (int, error) { r1, err := workflow.ExecuteActivity[int](ctx, Activity1, 35, 12).Get(ctx) if err != nil { return "", errors.Wrap(err, "could not get activity result") } logger.Debug("A1 result:", "r1", r1) return r1, nil } ``` -------------------------------- ### Execute Workflow with WorkflowOrchestrator in Go Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/index.html.md Demonstrates how to define a workflow, register it with activities, start the orchestrator, create a workflow instance, and retrieve its result directly. Use this for simpler scenarios without separate client/worker processes. ```go func MyWorkflow(ctx workflow.Context, input string) (string, error) { r1, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1).Get(ctx) if err != nil { return "", err } r2, err := workflow.ExecuteActivity[string](ctx, workflow.DefaultActivityOptions, FormatResult, input, r1).Get(ctx) if err != nil { return "", err } return r2, nil } func Activity1(ctx context.Context) (int, error) { return 42, nil } func FormatResult(ctx context.Context, input string, value int) (string, error) { return fmt.Sprintf("Processed %s with result %d", input, value), nil } func main() { ctx := context.Background() b := sqlite.NewSqliteBackend("simple.sqlite") // Create orchestrator instead of separate client and worker orchestrator := worker.NewWorkflowOrchestrator(b, nil) // Register workflows and activities explicitly orchestrator.RegisterWorkflow(MyWorkflow) orchestrator.RegisterActivity(Activity1) orchestrator.RegisterActivity(FormatResult) // Start the orchestrator if err := orchestrator.Start(ctx); err != nil { panic("could not start orchestrator") } // Create workflow instance wf, err := orchestrator.CreateWorkflowInstance(ctx, client.WorkflowInstanceOptions{ InstanceID: uuid.NewString(), }, MyWorkflow, "input-for-workflow") if err != nil { panic("could not start workflow") } // Get result directly result, err := client.GetWorkflowResult[string](ctx, orchestrator.Client, wf, 5*time.Second) if err != nil { panic("error getting workflow result: " + err.Error()) } fmt.Println("Workflow completed with result:", result) } ``` -------------------------------- ### Orchestrate Workflow Execution in Go Source: https://github.com/cschleiden/go-workflows/blob/main/README.md Combines worker setup, backend initialization, and workflow creation. Handles graceful shutdown via interrupt signals. ```go func main() { ctx := context.Background() b := sqlite.NewSqliteBackend("simple.sqlite") go runWorker(ctx, b) c := client.New(b) wf, err := c.CreateWorkflowInstance(ctx, client.WorkflowInstanceOptions{ InstanceID: uuid.NewString(), }, Workflow1, "input-for-workflow") if err != nil { panic("could not start workflow") } c2 := make(chan os.Signal, 1) signal.Notify(c2, os.Interrupt) <-c2 } ``` -------------------------------- ### Serve Diagnostics Web UI Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Sets up and starts an HTTP server to serve the diagnostic web UI for investigating workflow instances. It uses `diag.NewServeMux` to handle diagnostic requests. ```go m := http.NewServeMux() m.Handle("/diag/", http.StripPrefix("/diag", diag.NewServeMux(b))) go http.ListenAndServe(":3000", m) ``` -------------------------------- ### Configure Backend Logger Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Pass a custom logger to the backend when creating an instance using `backend.WithLogger`. This example sets the logger level to debug. ```go b := sqlite.NewInMemoryBackend(backend.WithLogger(slog.New(slog.Config{Level: slog.LevelDebug})) ``` -------------------------------- ### Run go-workflows Tests Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Commands to run short and full test suites, including race detection. Also includes installation and usage of go-junit-report for CI-compatible output. ```bash # Run short tests - Takes ~45 seconds. go test -short -timeout 120s -race -count 1 -v ./... # Run full test suite - Takes ~2.5 minutes. go test -timeout 180s -race -count 1 -v ./... # Install test reporting tool (if needed) go install github.com/jstemmer/go-junit-report/v2@latest # Run tests with JUnit output (as used in CI) - Takes ~2.5 minutes. go test -timeout 120s -race -count 1 -v ./... 2>&1 | go-junit-report -set-exit-code -iocopy -out "report.xml" ``` -------------------------------- ### Check Docker Service Status Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Verify that the development services started correctly by checking their status. ```bash docker compose ps ``` -------------------------------- ### Start Automatic Expiration (SQLite/MySQL) Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Call `StartAutoExpiration` on a client to initiate a background process that periodically removes old finished workflow instances. ```go client.StartAutoExpiration(ctx context.Context, delay time.Duration) ``` -------------------------------- ### Register and Run Default Worker Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Register workflows and activities with the default worker, then start it with a cancellable context. It's important to wait for completion upon shutdown. ```go defaultWorker.RegisterWorkflow(Workflow1) defaultWorker.RegisterActivity(Activity1) ctx, cancel := context.WithCancel(context.Background()) defaultWorker.Start(ctx) cancel() defaultWorker.WaitForCompletion() ``` -------------------------------- ### Register a Workflow with a Worker Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Register workflows with the worker before starting them. The function name is used as the workflow name. ```go var b backend.Backend w := worker.New(b) w.RegisterWorkflow(Workflow1) ``` -------------------------------- ### Get Activity Logger Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Retrieve a logger instance within an activity using `activity.Logger(ctx)`. The logger includes the activity ID and workflow instance as default fields. ```go logger := activity.Logger(ctx) ``` -------------------------------- ### Get Workflow Logger Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Retrieve a logger instance within a workflow using `workflow.Logger(ctx)`. The logger includes the workflow instance as a default field and trace IDs when tracing is enabled. ```go logger := workflow.Logger(ctx) ``` -------------------------------- ### Build Project Source: https://github.com/cschleiden/go-workflows/blob/main/DEVELOPMENT.md Run this command to build the project. Ensure dependencies are set up. ```bash make build ``` -------------------------------- ### Build Static Documentation Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Navigate to the 'docs' directory and execute the 'build.sh' script to generate static HTML documentation files in the 'build/' directory. This is suitable for deployment. ```bash cd docs ./build.sh ``` -------------------------------- ### Create New PostgreSQL Backend Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Use `NewPostgresBackend` to initialize a new PostgreSQL backend. This constructor requires connection details. ```go func NewPostgresBackend(host string, port int, user, password, database string, opts ...option) ``` -------------------------------- ### Create New Redis Backend Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Initialize a new Redis backend using `NewRedisBackend` with a provided `redis.UniversalClient`. Various options are available to configure its behavior. ```go func NewRedisBackend(client redis.UniversalClient, opts ...RedisBackendOption) ``` -------------------------------- ### Create New MySQL Backend Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Instantiate a new MySQL backend with connection details. Accepts various backend options. ```go func NewMysqlBackend(host string, port int, user, password, database string, opts ...option) ``` -------------------------------- ### Create New SQLite Backend Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Instantiate a new SQLite backend using its path. Accepts various backend options. ```go func NewSqliteBackend(path string, opts ...option) ``` -------------------------------- ### Initialize SQLite Backend Source: https://github.com/cschleiden/go-workflows/blob/main/README.md Create a new SQLite backend instance for persisting workflow events. Specify the database file path. ```go b := sqlite.NewSqliteBackend("simple.sqlite") ``` -------------------------------- ### Run Tests Source: https://github.com/cschleiden/go-workflows/blob/main/DEVELOPMENT.md Execute this command to run all project tests. Ensure dependencies are set up. ```bash make test ``` -------------------------------- ### Benchmark Utility Help Source: https://github.com/cschleiden/go-workflows/blob/main/bench/README.md Display the help message for the benchmark utility to view available command-line flags and their default values. This is useful for configuring the benchmark run. ```shell go run .h -h ``` -------------------------------- ### Create PostgreSQL Backend with Existing Connection Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Use `NewPostgresBackendWithDB` when you have an existing `*sql.DB` connection. The backend will not close this connection, and migrations are disabled by default. ```go func NewPostgresBackendWithDB(db *sql.DB, opts ...option) ``` ```go db, _ := sql.Open("pgx", "host=localhost port=5432 user=myuser password=mypass dbname=mydb sslmode=disable") backend := postgres.NewPostgresBackendWithDB(db, postgres.WithApplyMigrations(true)) ``` -------------------------------- ### Execute Activity Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Execute an activity using default options and await its result. ```go r1, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, 35, 12, nil, "test").Get(ctx) if err != nil { panic("error getting activity 1 result") } log.Println(r1) ``` -------------------------------- ### Create SQLite Backend with Existing DB Connection Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Create a SQLite backend using an existing *sql.DB connection. Migrations are disabled by default and the backend will not close the provided connection. ```go func NewSqliteBackendWithDB(db *sql.DB, opts ...option) ``` ```go db, _ := sql.Open("sqlite", "file:mydb.sqlite?_txlock=immediate") db.Exec("PRAGMA journal_mode=WAL;") db.Exec("PRAGMA busy_timeout = 5000;") db.SetMaxOpenConns(1) backend := sqlite.NewSqliteBackendWithDB(db, sqlite.WithApplyMigrations(true)) ``` -------------------------------- ### Lint go-workflows Code with Makefile Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Recommended method to run the full linter suite, including the custom analyzer, by using the project's Makefile. This ensures all configured linters are executed. ```bash # Run the full linter suite with custom analyzer - Takes ~12-15 seconds. make lint ``` -------------------------------- ### Create MySQL Backend with Existing DB Connection Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Create a MySQL backend using an existing *sql.DB connection. Migrations are disabled by default and the backend will not close the provided connection. A DSN supporting multi-statement queries is required to enable migrations. ```go func NewMysqlBackendWithDB(db *sql.DB, opts ...option) ``` ```go db, _ := sql.Open("mysql", "user:pass@tcp(localhost:3306)/mydb?parseTime=true") // To enable migrations, provide a DSN with multiStatements=true migrationDSN := "user:pass@tcp(localhost:3306)/mydb?parseTime=true&multiStatements=true" backend := mysql.NewMysqlBackendWithDB(db, mysql.WithApplyMigrations(true), mysql.WithMigrationDSN(migrationDSN), ) ``` -------------------------------- ### Define Activities Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Activities must accept context.Context as the first parameter. Parameters and return values must be serializable. Activities can return a result and an error. ```go func Activity1(ctx context.Context, a, b int) (int, error) { return a + b, nil } func Activity2(ctx context.Context) error { return a + b, nil } ``` -------------------------------- ### Run Benchmark Utility Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Execute the benchmark utility to verify hierarchical workflow creation and execution, checking that metrics output shows expected activity and workflow counts. ```bash cd bench && go run . ``` -------------------------------- ### Workflow Versioning with Conditional Execution Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_faq.md Demonstrates how to use `workflow.Version` to conditionally execute activities based on the workflow's version, ensuring backward compatibility. ```go func Workflow1(ctx workflow.Context) { r1, _ := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, 35, 12).Get(ctx) log.Println("A1 result:", r1) if workflow.Version(ctx) >= 2 { r3, _ := workflow.ExecuteActivity(ctx, workflow.DefaultActivityOptions, Activity3).Get(ctx) log.Println("A3 result:", r3) } r2, _ := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity2).Get(ctx) log.Println("A2 result:", r2) } ``` -------------------------------- ### Define a Basic Workflow Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Workflows must accept workflow.Context as the first parameter and return an error. Additional parameters must be serializable. ```go func Workflow1(ctx workflow.Context) error { } ``` -------------------------------- ### Send and Receive Signals in Go Workflows Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Demonstrates how to send a signal to a workflow instance from outside and how to receive a signal within a workflow. Signals can only be delivered to active workflow instances. ```go // From outside the workflow: c.SignalWorkflow(ctx, "", "signal-name", "value") func Workflow(ctx workflow.Context) error { // ... signalCh := workflow.NewSignalChannel[string](ctx, "signal-name") // Pause workflow until signal is received workflow.Select(ctx, workflow.Receive(signalCh, func(ctx workflow.Context, r string, ok bool) { logger.Debug("Received signal:", r) }), ) // Continue execution } ``` -------------------------------- ### Build Custom Analyzer Plugin Source: https://github.com/cschleiden/go-workflows/blob/main/DEVELOPMENT.md Build a custom analyzer plugin using Go. This command is used for advanced linting configurations. ```bash go build -tags analyzerplugin -buildmode=plugin analyzer/plugin/plugin.go ``` -------------------------------- ### Implement Go Activities Source: https://github.com/cschleiden/go-workflows/blob/main/README.md Activities can have side-effects and are executed once with results persisted. They do not need to be deterministic. ```go func Activity1(ctx context.Context, a, b int) (int, error) { return a + b, nil } ``` ```go func Activity2(ctx context.Context) (int, error) { return 12, nil } ``` -------------------------------- ### PostgreSQL Backend Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Configuration and usage of the PostgreSQL backend. ```APIDOC ## PostgreSQL Backend ### NewPostgresBackend **Description**: Creates a new PostgreSQL backend instance. **Function Signature**: `func NewPostgresBackend(host string, port int, user, password, database string, opts ...option)` ### NewPostgresBackendWithDB **Description**: Creates a new PostgreSQL backend instance using an existing `*sql.DB` connection. The backend will not close the connection, and migrations are disabled by default. **Function Signature**: `func NewPostgresBackendWithDB(db *sql.DB, opts ...option)` **Example Usage**: ```go db, _ := sql.Open("pgx", "host=localhost port=5432 user=myuser password=mypass dbname=mydb sslmode=disable") backend := postgres.NewPostgresBackendWithDB(db, postgres.WithApplyMigrations(true)) ``` ### Options - `WithPostgresOptions(f func(db *sql.DB))` - Apply custom options to the PostgreSQL database connection. - `WithApplyMigrations(applyMigrations bool)` - Set whether migrations should be applied on startup. Defaults to `true` for `NewPostgresBackend`, `false` for `NewPostgresBackendWithDB`. - `WithBackendOptions(opts ...backend.BackendOption)` - Apply generic backend options. ### Schema - `instances` - Tracks workflow instances and acts as an instance queue joined with `pending_events`. - `pending_events` - Stores pending events for workflow instances. - `history` - Stores the history for workflow instances. - `activities` - Acts as a queue for pending activities. - `attributes` - Stores payloads of events. ``` -------------------------------- ### End-to-End Simple Workflow Test Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Perform a simple end-to-end test of the workflow using the SQLite backend. The expected output indicates successful completion with a result of 59. ```bash cd samples/simple && go run . -backend sqlite ``` -------------------------------- ### Test Go Workflow with Mocked Activities Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Use `WorkflowTester` to mock activities and execute workflows for testing. Ensure all mocked expectations are met. ```go func TestWorkflow(t *testing.T) { ester := tester.NewWorkflowTester[int](Workflow1) // Mock two activities ester.OnActivity(Activity1, mock.Anything, 35, 12).Return(47, nil) ester.OnActivity(Activity2, mock.Anything, mock.Anything, mock.Anything).Return(12, nil) // Run workflow with inputs ester.Execute("Hello world") // Workflows always run to completion, or time-out require.True(t, tester.WorkflowFinished()) wr, werr := tester.WorkflowResult() require.Equal(t, 59, wr) require.Empty(t, werr) // Ensure any expectations set for activity or sub-workflow mocks were met ester.AssertExpectations(t) } ``` -------------------------------- ### Create Custom Activity Span Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Add custom spans to activities for detailed tracing. The `context.Context` passed into activities is pre-configured with the current span. ```go func Activity1(ctx context.Context, a, b int) (int, error) { ctx, span := otel.Tracer("activity1").Start(ctx, "Custom Activity1 span") defer span.End() // Do something } ``` -------------------------------- ### Benchmark Configuration Flags Source: https://github.com/cschleiden/go-workflows/blob/main/bench/README.md Lists and describes the available flags for configuring the benchmark utility. These flags control aspects like the number of workflows, sub-workflows, activities, and the backend used. ```shell -activities int Number of activities to execute per leaf workflow (default 2) -backend string Backend to use. Supported backends are: - redis - mysql - sqlite - postgres (default "redis") -cachesize int Size of the workflow executor cache (default 128) -depth int Depth of mid workflows (default 2) -fanout int Number of child workflows to execute per root/mid workflow (default 2) -format string Output format. Supported formats are: - text - csv (default "text") -leaffanout int Number of leaf workflows to execute per mid workflow (default 2) -resultsize int Size of activity result payload in bytes (default 100) -runs int Number of root workflows to start (default 1) -scenario string Scenario to run. Support scenarios are: - basic (default "basic") -timeout duration Timeout for the benchmark run (default 30s) ``` -------------------------------- ### Test Go Activity Function Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Test activities like regular functions. Use `activitytester.WithActivityTestState` to provide a test activity context if using activity context features like logging. ```go func Activity(ctx context.Context, a int, b int) (int, error) { activity.Logger(ctx).Debug("Activity is called", "a", a) return a + b, nil } func TestActivity(t *testing.T) { ctx := activitytester.WithActivityTestState(context.Background(), "activityID", "instanceID", nil) r, err := Activity(ctx, 35, 12) require.Equal(t, 47, r) require.NoError(t, err) } ``` -------------------------------- ### View Docker Service Logs Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Access the logs of the development services to troubleshoot any issues. ```bash docker compose logs ``` -------------------------------- ### Pre-Commit Validation Commands Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Essential commands to run before committing changes, including full build, short tests, linting, code formatting, and sample execution to ensure code quality and stability. ```bash # Full Build: go build -v ./... # Short Tests: go test -short -timeout 120s -race -count 1 -v ./... # Linting: make lint or golangci-lint run --timeout=5m # Code Formatting: make fmt # Sample Execution: At least one sample must run successfully ``` -------------------------------- ### Execute a Sub-Workflow on a Specific Queue in Go Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Specify a custom queue for a sub-workflow using `workflow.SubWorkflowInstanceOptions`. ```go result, err := workflow.CreateSubWorkflowInstance[int]( ctx, workflow.SubWorkflowInstanceOptions{ Queue: "my-queue", }, SubWorkflow, "some input").Get(ctx) if err != nil { return errors.Wrap(err, "could not get sub workflow result") } ``` -------------------------------- ### Configure Automatic Expiration (Redis) Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md When creating a Redis backend, pass `WithAutoExpiration` to automatically set TTLs on keys for finished workflow instances. ```go b, err := redis.NewRedisBackend(redisClient, redis.WithAutoExpiration(time.Hour * 48)) // ... ``` -------------------------------- ### Define Custom Backend Interface in Go Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Implement this Go interface to provide a custom backend for workflow management. It covers operations for creating, canceling, removing, and querying workflow instances, as well as managing workflow and activity tasks. ```go type Backend interface { // CreateWorkflowInstance creates a new workflow instance CreateWorkflowInstance(ctx context.Context, instance *workflow.Instance, event *history.Event) error // CancelWorkflowInstance cancels a running workflow instance CancelWorkflowInstance(ctx context.Context, instance *workflow.Instance, cancelEvent *history.Event) error // RemoveWorkflowInstance removes a workflow instance RemoveWorkflowInstance(ctx context.Context, instance *workflow.Instance) error // GetWorkflowInstanceState returns the state of the given workflow instance GetWorkflowInstanceState(ctx context.Context, instance *workflow.Instance) (core.WorkflowInstanceState, error) // GetWorkflowInstanceHistory returns the workflow history for the given instance. When lastSequenceID // is given, only events after that event are returned. Otherwise the full history is returned. GetWorkflowInstanceHistory(ctx context.Context, instance *workflow.Instance, lastSequenceID *int64) ([]*history.Event, error) // SignalWorkflow signals a running workflow instance // // If the given instance does not exist, it will return an error SignalWorkflow(ctx context.Context, instanceID string, event *history.Event) error // GetWorkflowTask returns a pending workflow task or nil if there are no pending workflow executions GetWorkflowTask(ctx context.Context) (*WorkflowTask, error) // ExtendWorkflowTask extends the lock of a workflow task ExtendWorkflowTask(ctx context.Context, taskID string, instance *core.WorkflowInstance) error // CompleteWorkflowTask checkpoints a workflow task retrieved using GetWorkflowTask // // This checkpoints the execution. events are new events from the last workflow execution // which will be added to the workflow instance history. workflowEvents are new events for the // completed or other workflow instances. CompleteWorkflowTask( ctx context.Context, task *WorkflowTask, instance *workflow.Instance, state core.WorkflowInstanceState, executedEvents, activityEvents, timerEvents []*history.Event, workflowEvents []history.WorkflowEvent) error // GetActivityTask returns a pending activity task or nil if there are no pending activities GetActivityTask(ctx context.Context) (*ActivityTask, error) // CompleteActivityTask completes an activity task retrieved using GetActivityTask CompleteActivityTask(ctx context.Context, instance *workflow.Instance, activityID string, event *history.Event) error // ExtendActivityTask extends the lock of an activity task ExtendActivityTask(ctx context.Context, activityID string) error // GetStats returns stats about the backend GetStats(ctx context.Context) (*Stats, error) // Logger returns the configured logger for the backend Logger() *slog.Logger // Tracer returns the configured trace provider for the backend Tracer() trace.Tracer // Metrics returns the configured metrics client for the backend Metrics() metrics.Client // Converter returns the configured converter for the backend Converter() converter.Converter // ContextPropagators returns the configured context propagators for the backend ContextPropagators() []workflow.ContextPropagator // Close closes any underlying resources Close() error } ``` -------------------------------- ### Select with Multiple Cases Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Use workflow.Select to handle multiple asynchronous operations like waiting for futures or receiving from channels. Cases are evaluated in the order they are provided. ```go var f1 workflow.Future[int] var c workflow.Channel[int] value := 42 workflow.Select( ctx, workflow.Await(f1, func (ctx workflow.Context, f Future[int]) { r, err := f.Get(ctx) // ... }), workflow.Receive(c, func (ctx workflow.Context, v int, ok bool) { // use v }), workflow.Default(ctx, func (ctx workflow.Context) { // ... }) ) ``` -------------------------------- ### Multi-Backend Workflow Test Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Test the simple workflow with different backends to ensure consistent behavior. Requires Docker Compose for the Redis backend and runs embedded for SQLite. ```bash # Test Redis backend (requires docker compose up) cd samples/simple && go run . # Test SQLite backend (embedded) cd samples/simple && go run . -backend sqlite ``` -------------------------------- ### Create Workers Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Instantiate different types of workers: a default combined worker, a workflow-only worker, and an activity-only worker. Ensure you have the necessary dependencies like `mb` and `b`. ```go defaultWorker := worker.New(mb, &worker.Options{}) workflowWorker := worker.NewWorkflowWorker(b, &worker.WorkflowWorkerOptions{}) activityWorker := worker.NewActivityWorker(b, &worker.ActivityWorkerOptions{}) ``` -------------------------------- ### Define Go Activities Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/index.html.md Activities are functions that can have side-effects and don't need to be deterministic. They receive a plain context.Context and are automatically retried by default. ```go func Activity1(ctx context.Context, a, b int) (int, error) { return a + b, nil } func Activity2(ctx context.Context) (int, error) { return 12, nil } ``` -------------------------------- ### Execute Activity on Specific Queue Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Execute an activity on a specified queue by providing custom ActivityOptions. ```go r1, err := workflow.ExecuteActivity[int](ctx, workflow.ActivityOptions{ Queue: "my-queue", }, Activity1, 35, 12, nil, "test").Get(ctx) if err != nil { panic("error getting activity 1 result") } log.Println(r1) ``` -------------------------------- ### Enable Custom Analyzer in .golangci.yaml Source: https://github.com/cschleiden/go-workflows/blob/main/analyzer/README.md Configure your .golangci.yaml to enable the custom goworkflows analyzer. Specify the module type and the original URL of the analyzer. ```yaml version: "2" linters: enable: - goworkflows settings: custom: goworkflows: type: module original-url: github.com/cschleiden/go-workflows/analyzer ``` -------------------------------- ### Define a Go Workflow Source: https://github.com/cschleiden/go-workflows/blob/main/README.md Workflows must be written in Go and avoid non-deterministic features. Inputs and outputs need to be serializable. ```go func Workflow1(ctx workflow.Context, input string) error { r1, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, 35, 12).Get(ctx) if err != nil { panic("error getting activity 1 result") } log.Println("A1 result:", r1) r2, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity2).Get(ctx) if err != nil { panic("error getting activity 1 result") } log.Println("A2 result:", r2) return nil } ``` -------------------------------- ### Basic Linting Workaround for go-workflows Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Use this command for basic linting if the custom analyzer encounters version compatibility issues. It disables all linters except for gofmt, govet, ineffassign, and misspell. ```bash # Run basic linting without custom analyzer - Takes ~12 seconds. golangci-lint run --disable-all --enable=gofmt,govet,ineffassign,misspell --timeout=5m ``` -------------------------------- ### Register Struct Method Activities Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Register activities as methods on a struct to provide shared state. The struct instance is passed to the worker. ```go type act struct { SharedState int } func (a *act) Activity1(ctx context.Context, a, b int) (int, error) { return a + b + act.SharedState, nil } func (a *act) Activity2(ctx context.Context, a int) (int, error) { return a * act.SharedState, nil } var w worker.Worker a := &act{ SharedState: 12, } w.RegisterActivity(a) ``` -------------------------------- ### Create Custom Workflow Span Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Create replay-aware spans within workflows using `workflow.Tracer(ctx)`. This allows for adding custom attributes to spans, such as messages. ```go func Workflow(ctx workflow.Context) error { ctx, span := workflow.Tracer(ctx).Start(ctx, "Workflow1 span", trace.WithAttributes( // Add additional attribute.String("msg", "hello world"), )) // Do something span.End() ``` -------------------------------- ### Redis Backend Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_backends.md Configuration and usage of the Redis backend. ```APIDOC ## Redis Backend ### NewRedisBackend **Description**: Creates a new Redis backend instance. **Function Signature**: `func NewRedisBackend(client redis.UniversalClient, opts ...RedisBackendOption)` ### Options - `WithKeyPrefix` - Set the key prefix for all keys. Defaults to `""`. - `WithBlockTimeout(timeout time.Duration)` - Set the timeout for blocking operations. Defaults to `5s`. - `WithAutoExpiration(expireFinishedRunsAfter time.Duration)` - Set the expiration time for finished runs. Defaults to `0` (never expires). - `WithAutoExpirationContinueAsNew(expireContinuedAsNewRunsAfter time.Duration)` - Set the expiration time for continued as new runs. Defaults to `0` (uses the same value as `WithAutoExpiration`). - `WithBackendOptions(opts ...backend.BackendOption)` - Apply generic backend options. ### Schema/Keys **Shared Keys**: - `instances-by-creation` - `ZSET` - Instances sorted by creation time. - `instances-active` - `SET` - Active instances. - `instances-expiring` - `SET` - Instances about to expire. - `task-queue:workflows` - `STREAM` - Task queue for workflows. - `task-queue:activities` - `STREAM` - Task queue for activities. **Instance Specific Keys**: - `active-instance-execution:{instanceID}` - Latest execution for a workflow instance. - `instance:{instanceID}:{executionID}` - State of the workflow instance. - `pending-events:{instanceID}:{executionID}` - `STREAM` - Pending events for a workflow instance. - `history:{instanceID}:{executionID}` - `STREAM` - History for a workflow instance. - `payload:{instanceID}:{executionID}` - `HASH` - Payloads of events for a given workflow instance. **Other Keys**: - `future-events` - `ZSET` - Events not yet visible, such as timer events. ``` -------------------------------- ### Handle Panics from Activities in Go Workflows Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Panics in activities are caught and exposed as `workflow.PanicError`. Access the stack trace using `perr.Stack()`. ```go r1, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, "test").Get(ctx) if err != nil { panic("error getting activity 1 result") } var perr *workflow.PanicError if errors.As(err, &perr) { logger.Error("Panic", "err", perr, "stack", perr.Stack()) return } ``` -------------------------------- ### Schedule Timer Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Schedule a timer to fire after a specified duration and await its completion. ```go t := workflow.ScheduleTimer(ctx, 2*time.Second) err := t.Get(ctx, nil) ``` -------------------------------- ### Register a Function Activity Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Register activities as functions with the worker. The name is inferred from the function name. ```go func Activity1(ctx context.Context, a, b int) (int, error) { return a + b, nil } var w worker.Worker w.RegisterActivity(Activity1) ``` -------------------------------- ### Cancel a Workflow Instance Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Create a `Client` instance and call `CancelWorkflowInstance` to cancel a workflow. Canceling a workflow cancels its context, causing subsequent scheduling calls to fail. ```go var c *client.Client err = c.CancelWorkflowInstance(context.Background(), workflowInstance) if err != nil { panic("could not cancel workflow") } ``` -------------------------------- ### Execute Side Effects in Go Workflows Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Illustrates how to use `workflow.SideEffect` for simple, one-time operations within a workflow that do not require scheduling an activity. The result is recorded in the history and replayed on subsequent executions. ```go id, _ := workflow.SideEffect[string](ctx, func(ctx workflow.Context) string) { return uuid.NewString() }).Get(ctx) ``` -------------------------------- ### Handle Custom and Panic Errors in Go Workflows Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Inspect workflow errors using `errors.As` to differentiate between custom errors and panics. Custom errors are identified by `werr.Type`, while panics are `workflow.PanicError`. ```go func handleError(ctx workflow.Context, logger log.Logger, err error) { var werr *workflow.Error if errors.As(err, &werr) { switch werr.Type { case "CustomError": // This was a `type CustomError struct...` returned by an activity/subworkflow logger.Error("Custom error", "err", werr) return } logger.Error("Generic workflow error", "err", werr, "stack", werr.Stack()) return } var perr *workflow.PanicError if errors.As(err, &perr) { // Activity/subworkflow ran into a panic logger.Error("Panic", "err", perr, "stack", perr.Stack()) return } logger.Error("Generic error", "err", err) } ``` -------------------------------- ### Restart Workflow with ContinueAsNew Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Use ContinueAsNew to restart a workflow with new inputs, helping to manage history size and performance. The workflow restarts with a fresh history but the same InstanceID. ```go wf := func(ctx workflow.Context, run int) (int, error) { run = run + 1 if run > 3 { return run, workflow.ContinueAsNew(ctx, run) } return run, nil } ``` -------------------------------- ### Configure Activity Retries and Permanent Errors in Go Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Activities with `DefaultActivityOptions` retry up to three times on error. Use `workflow.NewPermanentError` to prevent retries for specific error types. ```go func Activity1(ctx context.Context, name string) (int, error) { if name == "test" { // No need to retry in this case, the activity will aways fail with the given inputs return 0, workflow.NewPermanentError(errors.New("test is not a valid name")) } return http.Do("POST", "https://example.com", name) } ``` ```go func Activity1(ctx context.Context, name string) (int, error) { // Current retry attempt attempt := activity.Attempt(ctx) return http.Do("POST", "https://example.com", name) } ``` -------------------------------- ### Stop Development Services Source: https://github.com/cschleiden/go-workflows/blob/main/AGENTS.md Shut down the development services when they are no longer needed. ```bash docker compose down ``` -------------------------------- ### Signal Other Workflows from Within a Workflow Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Shows how to signal another workflow, such as a sub-workflow, from within an executing workflow. Ensure the target workflow instance exists before signaling. ```go func Workflow(ctx workflow.Context) error { if _, err := workflow.SignalWorkflow(ctx, "sub-instance-id", "signal-name", "value").Get(ctx); err != nil { // Handle error } } ``` -------------------------------- ### Execute Struct Method Activity Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Call activities registered as methods on a struct using workflow.ExecuteActivity. The shared state is accessible within the activity. ```go // ... var a *act r1, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, a.Activity1, 35, 12).Get(ctx) if err != nil { // handle error } // Output r1 = 47 + 12 (from the worker registration) = 59 ``` -------------------------------- ### Define ContextPropagator Interface Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Defines the interface for context propagation, including methods for injecting and extracting context between Go's context and workflow-specific context. ```go type ContextPropagator interface { Inject(context.Context, *Metadata) error Extract(context.Context, *Metadata) (context.Context, error) InjectFromWorkflow(Context, *Metadata) error ExtractToWorkflow(Context, *Metadata) (Context, error) } ``` -------------------------------- ### Receive from Channel in Select Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md The Receive function adds a case to workflow.Select to wait for a value to be available on a channel. This allows non-blocking channel reads within a workflow. ```go var c workflow.Channel[int] workflow.Select( ctx, workflow.Receive(c, func (ctx workflow.Context, v int, ok bool) { // ... }), ) ``` -------------------------------- ### Perform Cleanup in a Canceled Workflow Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md When a workflow is canceled, create a new disconnected context using `workflow.NewDisconnectedContext` to execute cleanup activities. Activities already running will complete, and their results will be available. ```go func Workflow2(ctx workflow.Context, msg string) (string, error) { defer func() { if errors.Is(ctx.Err(), workflow.Canceled) { // Workflow was canceled. Get new context to perform any cleanup activities ctx := workflow.NewDisconnectedContext(ctx) // Execute the cleanup activity if err := workflow.ExecuteActivity(ctx, ActivityCleanup).Get(ctx, nil); err != nil { return errors.Wrap(err, "could not perform cleanup") } } }() r1, err := workflow.ExecuteActivity[int](ctx, ActivityCancel, 1, 2).Get(ctx) if err != nil { // <---- Workflow is canceled while this activity is running return errors.Wrap(err, "could not get ActivityCancel result") } // r1 will contain the result of ActivityCancel // ⬇ ActivitySkip will be skipped immediately r2, err := workflow.ExecuteActivity(ctx, ActivitySkip, 1, 2).Get(ctx) if err != nil { return errors.Wrap(err, "could not get ActivitySkip result") } return "some result", nil } ``` -------------------------------- ### Schedule Named Timer Source: https://github.com/cschleiden/go-workflows/blob/main/docs/source/includes/_guide.md Schedule a timer with a specific name for tracing and debugging purposes. ```go t := workflow.ScheduleTimer(ctx, 2*time.Second, workflow.WithTimerName("my-timer")) ```