diff --git a/README.md b/README.md index 452f22dc..b2987b20 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,9 @@ Workflow Definition and an Activity Definition using API Key to authenticate wit These samples demonstrate some common control flow patterns using Temporal's Go SDK API. +- [**Dynamic Workflows**](./dynamic-workflows): Demonstrates how to execute Workflows and Activities dynamically, + using a single "Dynamic Workflow" + - [**Dynamic Execution**](./dynamic): Demonstrates how to execute Workflows and Activities using a name rather than a strongly typed function. diff --git a/dynamic-workflows/README.md b/dynamic-workflows/README.md new file mode 100644 index 00000000..715ae002 --- /dev/null +++ b/dynamic-workflows/README.md @@ -0,0 +1,15 @@ +# Dynamic Workflows and Activities + +The purpose of this sample is to demonstrate registering and using dynamic workflows and activities to +handle various workflow types at runtime. + +### Steps to run this sample: +1) Run a [Temporal service](https://github.com/temporalio/samples-go/tree/main/#how-to-use). +2) Run the following command to start the worker +``` +go run dynamic-workflows/worker/main.go +``` +3) Run the following command to start the example +``` +go run dynamic-workflows/starter/main.go +``` diff --git a/dynamic-workflows/activities.go b/dynamic-workflows/activities.go new file mode 100644 index 00000000..4b28028c --- /dev/null +++ b/dynamic-workflows/activities.go @@ -0,0 +1,20 @@ +package dynamic_workflows + +import ( + "context" + "fmt" + "go.temporal.io/sdk/activity" + "go.temporal.io/sdk/converter" +) + +func DynamicActivity(ctx context.Context, args converter.EncodedValues) (string, error) { + var arg1, arg2 string + err := args.Get(&arg1, &arg2) + if err != nil { + return "", fmt.Errorf("failed to decode arguments: %w", err) + } + + info := activity.GetInfo(ctx) + result := fmt.Sprintf("%s - %s - %s", info.WorkflowType.Name, arg1, arg2) + return result, nil +} diff --git a/dynamic-workflows/starter/main.go b/dynamic-workflows/starter/main.go new file mode 100644 index 00000000..34a0b2c8 --- /dev/null +++ b/dynamic-workflows/starter/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + "log" + + "github.com/pborman/uuid" + "go.temporal.io/sdk/client" +) + +func main() { + // The client is a heavyweight object that should be created once per process. + c, err := client.Dial(client.Options{ + HostPort: client.DefaultHostPort, + }) + if err != nil { + log.Fatalln("Unable to create client", err) + } + defer c.Close() + + // This workflow ID can be user business logic identifier as well. + workflowID := "dynamic_workflows_" + uuid.New() + workflowOptions := client.StartWorkflowOptions{ + ID: workflowID, + TaskQueue: "dynamic-workflows", + } + + we, err := c.ExecuteWorkflow(context.Background(), workflowOptions, "DurableExecution", "Hello", "Temporal") + if err != nil { + log.Fatalln("Unable to execute workflow", err) + } + log.Println("Started workflow", "WorkflowID", we.GetID(), "RunID", we.GetRunID()) + + var result string + err = we.Get(context.Background(), &result) + if err != nil { + log.Fatalln("Unable get workflow result", err) + } + log.Println("Workflow result:", result) + + we, err = c.ExecuteWorkflow(context.Background(), workflowOptions, "dynamic-activity", "Peanut", "Butter") + if err != nil { + log.Fatalln("Unable to execute workflow", err) + } + log.Println("Started workflow", "WorkflowID", we.GetID(), "RunID", we.GetRunID()) + + err = we.Get(context.Background(), &result) + if err != nil { + log.Fatalln("Unable get workflow result", err) + } + log.Println("Workflow result:", result) + + we, err = c.ExecuteWorkflow(context.Background(), workflowOptions, "dynamic-activity123", "Jelly", "Time") + if err != nil { + log.Fatalln("Unable to execute workflow", err) + } + log.Println("Started workflow", "WorkflowID", we.GetID(), "RunID", we.GetRunID()) + + err = we.Get(context.Background(), &result) + if err != nil { + log.Fatalln("Unable get workflow result", err) + } + log.Println("Workflow result:", result) +} diff --git a/dynamic-workflows/worker/main.go b/dynamic-workflows/worker/main.go new file mode 100644 index 00000000..1c692e84 --- /dev/null +++ b/dynamic-workflows/worker/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "go.temporal.io/sdk/activity" + "go.temporal.io/sdk/workflow" + "log" + + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" + + dynamic "github.com/temporalio/samples-go/dynamic-workflows" +) + +func main() { + // The client and worker are heavyweight objects that should be created once per process. + c, err := client.Dial(client.Options{ + HostPort: client.DefaultHostPort, + }) + if err != nil { + log.Fatalln("Unable to create client", err) + } + defer c.Close() + + w := worker.New(c, "dynamic-workflows", worker.Options{}) + + w.RegisterDynamicWorkflow(dynamic.DynamicWorkflow, workflow.DynamicRegisterOptions{}) + w.RegisterDynamicActivity(dynamic.DynamicActivity, activity.DynamicRegisterOptions{}) + + err = w.Run(worker.InterruptCh()) + if err != nil { + log.Fatalln("Unable to start worker", err) + } +} diff --git a/dynamic-workflows/workflow.go b/dynamic-workflows/workflow.go new file mode 100644 index 00000000..f1651af5 --- /dev/null +++ b/dynamic-workflows/workflow.go @@ -0,0 +1,31 @@ +package dynamic_workflows + +import ( + "fmt" + "go.temporal.io/sdk/converter" + "go.temporal.io/sdk/workflow" + "strings" + "time" +) + +func DynamicWorkflow(ctx workflow.Context, args converter.EncodedValues) (string, error) { + var result string + info := workflow.GetInfo(ctx) + + var arg1, arg2 string + err := args.Get(&arg1, &arg2) + if err != nil { + return "", fmt.Errorf("failed to decode arguments: %w", err) + } + + if strings.HasPrefix(info.WorkflowType.Name, "dynamic-activity") { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{StartToCloseTimeout: 10 * time.Second}) + err := workflow.ExecuteActivity(ctx, "random-activity-name", arg1, arg2).Get(ctx, &result) + if err != nil { + return "", err + } + } else { + result = fmt.Sprintf("%s - %s - %s", info.WorkflowType.Name, arg1, arg2) + } + return result, nil +} diff --git a/dynamic-workflows/workflow_test.go b/dynamic-workflows/workflow_test.go new file mode 100644 index 00000000..a1452ceb --- /dev/null +++ b/dynamic-workflows/workflow_test.go @@ -0,0 +1,23 @@ +package dynamic_workflows + +import ( + "github.com/stretchr/testify/assert" + "go.temporal.io/sdk/activity" + "go.temporal.io/sdk/testsuite" + "go.temporal.io/sdk/workflow" + "testing" +) + +func TestDynamicWorkflow(t *testing.T) { + s := testsuite.WorkflowTestSuite{} + env := s.NewTestWorkflowEnvironment() + env.RegisterDynamicWorkflow(DynamicWorkflow, workflow.DynamicRegisterOptions{}) + env.RegisterDynamicActivity(DynamicActivity, activity.DynamicRegisterOptions{}) + env.ExecuteWorkflow("dynamic-activity", "Hello", "World") + assert.True(t, env.IsWorkflowCompleted()) + assert.NoError(t, env.GetWorkflowError()) + var result string + err := env.GetWorkflowResult(&result) + assert.NoError(t, err) + assert.Equal(t, "dynamic-activity - Hello - World", result) +}