diff --git a/new_samples/childworkflow/childworkflow.go b/new_samples/childworkflow/childworkflow.go new file mode 100644 index 0000000..5c2b2f0 --- /dev/null +++ b/new_samples/childworkflow/childworkflow.go @@ -0,0 +1,64 @@ +package main + +import ( + "errors" + "fmt" + "time" + + "go.uber.org/cadence/workflow" + "go.uber.org/zap" +) + +// ParentWorkflow demonstrates invoking a child workflow from a parent. +// The parent waits for the child to complete before finishing. +func ParentWorkflow(ctx workflow.Context) error { + logger := workflow.GetLogger(ctx) + logger.Info("ParentWorkflow started") + + execution := workflow.GetInfo(ctx).WorkflowExecution + // Parent can specify its own ID for child execution + childID := fmt.Sprintf("child_workflow:%v", execution.RunID) + + cwo := workflow.ChildWorkflowOptions{ + WorkflowID: childID, + ExecutionStartToCloseTimeout: time.Minute, + } + ctx = workflow.WithChildOptions(ctx, cwo) + + // Execute child workflow and wait for result + var result string + err := workflow.ExecuteChildWorkflow(ctx, ChildWorkflow, 0, 5).Get(ctx, &result) + if err != nil { + logger.Error("Child workflow failed", zap.Error(err)) + return err + } + + logger.Info("ParentWorkflow completed", zap.String("result", result)) + return nil +} + +// ChildWorkflow demonstrates ContinueAsNew pattern. +// It runs multiple times, restarting itself with ContinueAsNew until runCount reaches 0. +func ChildWorkflow(ctx workflow.Context, totalCount, runCount int) (string, error) { + logger := workflow.GetLogger(ctx) + logger.Info("ChildWorkflow started", zap.Int("totalCount", totalCount), zap.Int("runCount", runCount)) + + if runCount <= 0 { + logger.Error("Invalid run count", zap.Int("runCount", runCount)) + return "", errors.New("invalid run count") + } + + totalCount++ + runCount-- + + if runCount == 0 { + result := fmt.Sprintf("Child workflow completed after %d runs", totalCount) + logger.Info("ChildWorkflow completed", zap.String("result", result)) + return result, nil + } + + // ContinueAsNew: start a new run with fresh history + logger.Info("ChildWorkflow continuing as new", zap.Int("remainingRuns", runCount)) + return "", workflow.NewContinueAsNewError(ctx, ChildWorkflow, totalCount, runCount) +} + diff --git a/new_samples/childworkflow/generator/README.md b/new_samples/childworkflow/generator/README.md new file mode 100644 index 0000000..1da3502 --- /dev/null +++ b/new_samples/childworkflow/generator/README.md @@ -0,0 +1,23 @@ + + + +# Sample Generator + +This folder is NOT part of the actual sample. It exists only for contributors who work on this sample. Please disregard it if you are trying to learn about Cadence. + +To create a better learning experience for Cadence users, each sample folder is designed to be self contained. Users can view every part of writing and running workflows, including: + +* Cadence client initialization +* Worker with workflow and activity registrations +* Workflow starter +* and the workflow code itself + +Some samples may have more or fewer parts depending on what they need to demonstrate. + +In most cases, the workflow code (e.g. `workflow.go`) is the part that users care about. The rest is boilerplate needed to run that workflow. For each sample folder, the workflow code should be written by hand. The boilerplate can be generated. Keeping all parts inside one folder gives early learners more value because they can see everything together rather than jumping across directories. + +## Contributing + +* When creating a new sample, follow the steps mentioned in the README file in the main samples folder. +* To update the sample workflow code, edit the workflow file directly. +* To update the worker, client, or other boilerplate logic, edit the generator file. If your change applies to all samples, update the common generator file inside the `template` folder. Edit the generator file in this folder only when the change should affect this sample alone. diff --git a/new_samples/childworkflow/generator/README_specific.md b/new_samples/childworkflow/generator/README_specific.md new file mode 100644 index 0000000..16adb4f --- /dev/null +++ b/new_samples/childworkflow/generator/README_specific.md @@ -0,0 +1,76 @@ +## Child Workflow Sample + +This sample demonstrates **parent-child workflow relationships** and the **ContinueAsNew** pattern. + +### Start the Workflow + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow start \ + --tl cadence-samples-worker \ + --et 60 \ + --workflow_type cadence_samples.ParentWorkflow +``` + +### What Happens + +``` +┌─────────────────────┐ +│ ParentWorkflow │ +└──────────┬──────────┘ + │ + │ ExecuteChildWorkflow + ▼ +┌─────────────────────┐ +│ ChildWorkflow │──┐ +│ (run 1 of 5) │ │ +└─────────────────────┘ │ + │ │ ContinueAsNew + ▼ │ +┌─────────────────────┐ │ +│ ChildWorkflow │──┤ +│ (run 2 of 5) │ │ +└─────────────────────┘ │ + │ │ + ... ... + │ │ + ▼ │ +┌─────────────────────┐ │ +│ ChildWorkflow │◀─┘ +│ (run 5 of 5) │ +└─────────────────────┘ + │ + │ Returns result + ▼ +┌─────────────────────┐ +│ ParentWorkflow │ +│ completes │ +└─────────────────────┘ +``` + +1. Parent workflow starts a child workflow +2. Child workflow uses `ContinueAsNew` to restart itself 5 times +3. After 5 runs, child completes and returns result to parent + +### Key Concept: Child Workflow Options + +```go +cwo := workflow.ChildWorkflowOptions{ + WorkflowID: childID, + ExecutionStartToCloseTimeout: time.Minute, +} +ctx = workflow.WithChildOptions(ctx, cwo) + +err := workflow.ExecuteChildWorkflow(ctx, ChildWorkflow, args...).Get(ctx, &result) +``` + +### Key Concept: ContinueAsNew + +```go +// Instead of recursion (which grows history), use ContinueAsNew +return "", workflow.NewContinueAsNewError(ctx, ChildWorkflow, newArgs...) +``` + +ContinueAsNew starts a new workflow run with fresh history, avoiding unbounded history growth. + diff --git a/new_samples/childworkflow/generator/generate.go b/new_samples/childworkflow/generator/generate.go new file mode 100644 index 0000000..13962f2 --- /dev/null +++ b/new_samples/childworkflow/generator/generate.go @@ -0,0 +1,14 @@ +package main + +import "github.com/uber-common/cadence-samples/new_samples/template" + +func main() { + data := template.TemplateData{ + SampleName: "Child Workflow", + Workflows: []string{"ParentWorkflow", "ChildWorkflow"}, + Activities: []string{}, + } + + template.GenerateAll(data) +} + diff --git a/new_samples/childworkflow/main.go b/new_samples/childworkflow/main.go new file mode 100644 index 0000000..5893999 --- /dev/null +++ b/new_samples/childworkflow/main.go @@ -0,0 +1,20 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" +) + +func main() { + StartWorker() + + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT) + fmt.Println("Cadence worker started, press ctrl+c to terminate...") + <-done +} diff --git a/new_samples/childworkflow/worker.go b/new_samples/childworkflow/worker.go new file mode 100644 index 0000000..6e101de --- /dev/null +++ b/new_samples/childworkflow/worker.go @@ -0,0 +1,100 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +// Package worker implements a Cadence worker with basic configurations. +package main + +import ( + "github.com/uber-go/tally" + apiv1 "github.com/uber/cadence-idl/go/proto/api/v1" + "go.uber.org/cadence/.gen/go/cadence/workflowserviceclient" + "go.uber.org/cadence/compatibility" + "go.uber.org/cadence/worker" + "go.uber.org/cadence/workflow" + "go.uber.org/yarpc" + "go.uber.org/yarpc/peer" + yarpchostport "go.uber.org/yarpc/peer/hostport" + "go.uber.org/yarpc/transport/grpc" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +const ( + HostPort = "127.0.0.1:7833" + Domain = "cadence-samples" + // TaskListName identifies set of client workflows, activities, and workers. + // It could be your group or client or application name. + TaskListName = "cadence-samples-worker" + ClientName = "cadence-samples-worker" + CadenceService = "cadence-frontend" +) + +// StartWorker creates and starts a basic Cadence worker. +func StartWorker() { + logger, cadenceClient := BuildLogger(), BuildCadenceClient() + workerOptions := worker.Options{ + Logger: logger, + MetricsScope: tally.NewTestScope(TaskListName, nil), + } + + w := worker.New( + cadenceClient, + Domain, + TaskListName, + workerOptions) + // HelloWorld workflow registration + w.RegisterWorkflowWithOptions(ParentWorkflow, workflow.RegisterOptions{Name: "cadence_samples.ParentWorkflow"}) + w.RegisterWorkflowWithOptions(ChildWorkflow, workflow.RegisterOptions{Name: "cadence_samples.ChildWorkflow"}) + + err := w.Start() + if err != nil { + panic("Failed to start worker: " + err.Error()) + } + logger.Info("Started Worker.", zap.String("worker", TaskListName)) + +} + +func BuildCadenceClient(dialOptions ...grpc.DialOption) workflowserviceclient.Interface { + grpcTransport := grpc.NewTransport() + // Create a single peer chooser that identifies the host/port and configures + // a gRPC dialer with TLS credentials + myChooser := peer.NewSingle( + yarpchostport.Identify(HostPort), + grpcTransport.NewDialer(dialOptions...), + ) + outbound := grpcTransport.NewOutbound(myChooser) + + dispatcher := yarpc.NewDispatcher(yarpc.Config{ + Name: ClientName, + Outbounds: yarpc.Outbounds{ + CadenceService: {Unary: outbound}, + }, + }) + if err := dispatcher.Start(); err != nil { + panic("Failed to start dispatcher: " + err.Error()) + } + + clientConfig := dispatcher.ClientConfig(CadenceService) + + // Create a compatibility adapter that wraps proto-based YARPC clients + // to provide a unified interface for domain, workflow, worker, and visibility APIs + return compatibility.NewThrift2ProtoAdapter( + apiv1.NewDomainAPIYARPCClient(clientConfig), + apiv1.NewWorkflowAPIYARPCClient(clientConfig), + apiv1.NewWorkerAPIYARPCClient(clientConfig), + apiv1.NewVisibilityAPIYARPCClient(clientConfig), + ) +} + +func BuildLogger() *zap.Logger { + config := zap.NewDevelopmentConfig() + config.Level.SetLevel(zapcore.InfoLevel) + + var err error + logger, err := config.Build() + if err != nil { + panic("Failed to setup logger: " + err.Error()) + } + + return logger +} diff --git a/new_samples/localactivity/generator/README.md b/new_samples/localactivity/generator/README.md new file mode 100644 index 0000000..1da3502 --- /dev/null +++ b/new_samples/localactivity/generator/README.md @@ -0,0 +1,23 @@ + + + +# Sample Generator + +This folder is NOT part of the actual sample. It exists only for contributors who work on this sample. Please disregard it if you are trying to learn about Cadence. + +To create a better learning experience for Cadence users, each sample folder is designed to be self contained. Users can view every part of writing and running workflows, including: + +* Cadence client initialization +* Worker with workflow and activity registrations +* Workflow starter +* and the workflow code itself + +Some samples may have more or fewer parts depending on what they need to demonstrate. + +In most cases, the workflow code (e.g. `workflow.go`) is the part that users care about. The rest is boilerplate needed to run that workflow. For each sample folder, the workflow code should be written by hand. The boilerplate can be generated. Keeping all parts inside one folder gives early learners more value because they can see everything together rather than jumping across directories. + +## Contributing + +* When creating a new sample, follow the steps mentioned in the README file in the main samples folder. +* To update the sample workflow code, edit the workflow file directly. +* To update the worker, client, or other boilerplate logic, edit the generator file. If your change applies to all samples, update the common generator file inside the `template` folder. Edit the generator file in this folder only when the change should affect this sample alone. diff --git a/new_samples/localactivity/generator/README_specific.md b/new_samples/localactivity/generator/README_specific.md new file mode 100644 index 0000000..f45ea14 --- /dev/null +++ b/new_samples/localactivity/generator/README_specific.md @@ -0,0 +1,65 @@ +## Local Activity Sample + +This sample demonstrates **local activities** - lightweight activities that run in the workflow worker process. + +### Start the Workflow + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow start \ + --tl cadence-samples-worker \ + --et 60 \ + --workflow_type cadence_samples.LocalActivityWorkflow \ + --input '"test_0_1_data"' +``` + +### What Happens + +The workflow uses local activities to quickly check conditions, then runs regular activities only for matching conditions: + +``` +Input: "test_0_1_data" + +Local Activities (fast, no server round-trip): + ├── CheckCondition0("test_0_1_data") → true (contains "_0_") + ├── CheckCondition1("test_0_1_data") → true (contains "_1_") + └── CheckCondition2("test_0_1_data") → false (no "_2_") + +Regular Activities (only for matching conditions): + ├── ProcessActivity(0) → runs + └── ProcessActivity(1) → runs +``` + +### Key Concept: Local vs Regular Activity + +```go +// Local activity - runs in worker process, no server round-trip +lao := workflow.LocalActivityOptions{ + ScheduleToCloseTimeout: time.Second, +} +ctx = workflow.WithLocalActivityOptions(ctx, lao) +workflow.ExecuteLocalActivity(ctx, checkCondition, data) + +// Regular activity - scheduled through server +ao := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, +} +ctx = workflow.WithActivityOptions(ctx, ao) +workflow.ExecuteActivity(ctx, processActivity, data) +``` + +### When to Use Local Activities + +✅ **Good for:** +- Fast validations/checks +- Data transformations +- Condition evaluation +- Operations < 1 second + +❌ **Avoid for:** +- Long-running operations +- Operations needing retries +- External API calls + diff --git a/new_samples/localactivity/generator/generate.go b/new_samples/localactivity/generator/generate.go new file mode 100644 index 0000000..c7ca17d --- /dev/null +++ b/new_samples/localactivity/generator/generate.go @@ -0,0 +1,14 @@ +package main + +import "github.com/uber-common/cadence-samples/new_samples/template" + +func main() { + data := template.TemplateData{ + SampleName: "Local Activity", + Workflows: []string{"LocalActivityWorkflow"}, + Activities: []string{"ProcessActivity"}, + } + + template.GenerateAll(data) +} + diff --git a/new_samples/localactivity/localactivity.go b/new_samples/localactivity/localactivity.go new file mode 100644 index 0000000..38c7f48 --- /dev/null +++ b/new_samples/localactivity/localactivity.go @@ -0,0 +1,96 @@ +package main + +import ( + "context" + "strings" + "time" + + "go.uber.org/cadence/activity" + "go.uber.org/cadence/workflow" + "go.uber.org/zap" +) + +// Condition checkers - these run as local activities +var conditionCheckers = []func(context.Context, string) (bool, error){ + checkCondition0, + checkCondition1, + checkCondition2, +} + +// LocalActivityWorkflow demonstrates local activities for fast condition checking. +// Local activities run in the worker process without server round-trips. +func LocalActivityWorkflow(ctx workflow.Context, data string) (string, error) { + logger := workflow.GetLogger(ctx) + logger.Info("LocalActivityWorkflow started", zap.String("data", data)) + + // Local activity options - short timeout since they run locally + lao := workflow.LocalActivityOptions{ + ScheduleToCloseTimeout: time.Second, + } + ctx = workflow.WithLocalActivityOptions(ctx, lao) + + // Regular activity options + ao := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + } + ctx = workflow.WithActivityOptions(ctx, ao) + + var activityFutures []workflow.Future + + // Use local activities to quickly check conditions + for i, checker := range conditionCheckers { + var conditionMet bool + err := workflow.ExecuteLocalActivity(ctx, checker, data).Get(ctx, &conditionMet) + if err != nil { + return "", err + } + + logger.Info("Condition checked", zap.Int("condition", i), zap.Bool("met", conditionMet)) + + // Only schedule regular activity if condition is met + if conditionMet { + f := workflow.ExecuteActivity(ctx, ProcessActivity, i) + activityFutures = append(activityFutures, f) + } + } + + // Collect results from activities that were scheduled + var result string + for _, f := range activityFutures { + var activityResult string + if err := f.Get(ctx, &activityResult); err != nil { + return "", err + } + result += activityResult + " " + } + + logger.Info("LocalActivityWorkflow completed", zap.String("result", result)) + return result, nil +} + +// Local activity functions - these run in worker process, not scheduled through server + +func checkCondition0(ctx context.Context, data string) (bool, error) { + return strings.Contains(data, "_0_"), nil +} + +func checkCondition1(ctx context.Context, data string) (bool, error) { + return strings.Contains(data, "_1_"), nil +} + +func checkCondition2(ctx context.Context, data string) (bool, error) { + return strings.Contains(data, "_2_"), nil +} + +// ProcessActivity is a regular activity that processes data for a matched condition. +func ProcessActivity(ctx context.Context, conditionID int) (string, error) { + logger := activity.GetLogger(ctx) + logger.Info("ProcessActivity running", zap.Int("conditionID", conditionID)) + + // Simulate processing + time.Sleep(time.Second) + + return "processed_" + string(rune('0'+conditionID)), nil +} + diff --git a/new_samples/localactivity/main.go b/new_samples/localactivity/main.go new file mode 100644 index 0000000..5893999 --- /dev/null +++ b/new_samples/localactivity/main.go @@ -0,0 +1,20 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" +) + +func main() { + StartWorker() + + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT) + fmt.Println("Cadence worker started, press ctrl+c to terminate...") + <-done +} diff --git a/new_samples/localactivity/worker.go b/new_samples/localactivity/worker.go new file mode 100644 index 0000000..0a3cf87 --- /dev/null +++ b/new_samples/localactivity/worker.go @@ -0,0 +1,101 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +// Package worker implements a Cadence worker with basic configurations. +package main + +import ( + "github.com/uber-go/tally" + apiv1 "github.com/uber/cadence-idl/go/proto/api/v1" + "go.uber.org/cadence/.gen/go/cadence/workflowserviceclient" + "go.uber.org/cadence/activity" + "go.uber.org/cadence/compatibility" + "go.uber.org/cadence/worker" + "go.uber.org/cadence/workflow" + "go.uber.org/yarpc" + "go.uber.org/yarpc/peer" + yarpchostport "go.uber.org/yarpc/peer/hostport" + "go.uber.org/yarpc/transport/grpc" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +const ( + HostPort = "127.0.0.1:7833" + Domain = "cadence-samples" + // TaskListName identifies set of client workflows, activities, and workers. + // It could be your group or client or application name. + TaskListName = "cadence-samples-worker" + ClientName = "cadence-samples-worker" + CadenceService = "cadence-frontend" +) + +// StartWorker creates and starts a basic Cadence worker. +func StartWorker() { + logger, cadenceClient := BuildLogger(), BuildCadenceClient() + workerOptions := worker.Options{ + Logger: logger, + MetricsScope: tally.NewTestScope(TaskListName, nil), + } + + w := worker.New( + cadenceClient, + Domain, + TaskListName, + workerOptions) + // HelloWorld workflow registration + w.RegisterWorkflowWithOptions(LocalActivityWorkflow, workflow.RegisterOptions{Name: "cadence_samples.LocalActivityWorkflow"}) + w.RegisterActivityWithOptions(ProcessActivity, activity.RegisterOptions{Name: "cadence_samples.ProcessActivity"}) + + err := w.Start() + if err != nil { + panic("Failed to start worker: " + err.Error()) + } + logger.Info("Started Worker.", zap.String("worker", TaskListName)) + +} + +func BuildCadenceClient(dialOptions ...grpc.DialOption) workflowserviceclient.Interface { + grpcTransport := grpc.NewTransport() + // Create a single peer chooser that identifies the host/port and configures + // a gRPC dialer with TLS credentials + myChooser := peer.NewSingle( + yarpchostport.Identify(HostPort), + grpcTransport.NewDialer(dialOptions...), + ) + outbound := grpcTransport.NewOutbound(myChooser) + + dispatcher := yarpc.NewDispatcher(yarpc.Config{ + Name: ClientName, + Outbounds: yarpc.Outbounds{ + CadenceService: {Unary: outbound}, + }, + }) + if err := dispatcher.Start(); err != nil { + panic("Failed to start dispatcher: " + err.Error()) + } + + clientConfig := dispatcher.ClientConfig(CadenceService) + + // Create a compatibility adapter that wraps proto-based YARPC clients + // to provide a unified interface for domain, workflow, worker, and visibility APIs + return compatibility.NewThrift2ProtoAdapter( + apiv1.NewDomainAPIYARPCClient(clientConfig), + apiv1.NewWorkflowAPIYARPCClient(clientConfig), + apiv1.NewWorkerAPIYARPCClient(clientConfig), + apiv1.NewVisibilityAPIYARPCClient(clientConfig), + ) +} + +func BuildLogger() *zap.Logger { + config := zap.NewDevelopmentConfig() + config.Level.SetLevel(zapcore.InfoLevel) + + var err error + logger, err := config.Build() + if err != nil { + panic("Failed to setup logger: " + err.Error()) + } + + return logger +} diff --git a/new_samples/query/generator/README.md b/new_samples/query/generator/README.md new file mode 100644 index 0000000..1da3502 --- /dev/null +++ b/new_samples/query/generator/README.md @@ -0,0 +1,23 @@ + + + +# Sample Generator + +This folder is NOT part of the actual sample. It exists only for contributors who work on this sample. Please disregard it if you are trying to learn about Cadence. + +To create a better learning experience for Cadence users, each sample folder is designed to be self contained. Users can view every part of writing and running workflows, including: + +* Cadence client initialization +* Worker with workflow and activity registrations +* Workflow starter +* and the workflow code itself + +Some samples may have more or fewer parts depending on what they need to demonstrate. + +In most cases, the workflow code (e.g. `workflow.go`) is the part that users care about. The rest is boilerplate needed to run that workflow. For each sample folder, the workflow code should be written by hand. The boilerplate can be generated. Keeping all parts inside one folder gives early learners more value because they can see everything together rather than jumping across directories. + +## Contributing + +* When creating a new sample, follow the steps mentioned in the README file in the main samples folder. +* To update the sample workflow code, edit the workflow file directly. +* To update the worker, client, or other boilerplate logic, edit the generator file. If your change applies to all samples, update the common generator file inside the `template` folder. Edit the generator file in this folder only when the change should affect this sample alone. diff --git a/new_samples/query/generator/README_specific.md b/new_samples/query/generator/README_specific.md new file mode 100644 index 0000000..a663181 --- /dev/null +++ b/new_samples/query/generator/README_specific.md @@ -0,0 +1,62 @@ +## Query Workflow Sample + +This sample demonstrates **workflow queries** - inspecting workflow state without affecting execution. + +### Start the Workflow + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow start \ + --tl cadence-samples-worker \ + --et 180 \ + --workflow_type cadence_samples.QueryWorkflow +``` + +### Query the Workflow + +While the workflow is running, query its state: + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow query \ + --wid \ + --qt state +``` + +### What Happens + +The workflow goes through states that you can query: + +``` +Time 0: state = "started" +Time 1s: state = "waiting on timer" +Time 2m: state = "done" (workflow completes) +``` + +### Key Concept: Query Handler + +```go +func QueryWorkflow(ctx workflow.Context) error { + currentState := "started" + + // Register query handler for "state" query type + workflow.SetQueryHandler(ctx, "state", func() (string, error) { + return currentState, nil + }) + + currentState = "waiting on timer" + workflow.NewTimer(ctx, 2*time.Minute).Get(ctx, nil) + + currentState = "done" + return nil +} +``` + +### Use Cases + +- Progress monitoring dashboards +- Debugging running workflows +- Health checks without affecting execution + diff --git a/new_samples/query/generator/generate.go b/new_samples/query/generator/generate.go new file mode 100644 index 0000000..b66c54d --- /dev/null +++ b/new_samples/query/generator/generate.go @@ -0,0 +1,14 @@ +package main + +import "github.com/uber-common/cadence-samples/new_samples/template" + +func main() { + data := template.TemplateData{ + SampleName: "Query", + Workflows: []string{"QueryWorkflow"}, + Activities: []string{}, + } + + template.GenerateAll(data) +} + diff --git a/new_samples/query/main.go b/new_samples/query/main.go new file mode 100644 index 0000000..5893999 --- /dev/null +++ b/new_samples/query/main.go @@ -0,0 +1,20 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" +) + +func main() { + StartWorker() + + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT) + fmt.Println("Cadence worker started, press ctrl+c to terminate...") + <-done +} diff --git a/new_samples/query/query.go b/new_samples/query/query.go new file mode 100644 index 0000000..3fae35f --- /dev/null +++ b/new_samples/query/query.go @@ -0,0 +1,38 @@ +package main + +import ( + "time" + + "go.uber.org/cadence/workflow" +) + +// QueryWorkflow demonstrates query handlers for inspecting workflow state. +// Query the workflow with: cadence workflow query --wid --qt state +func QueryWorkflow(ctx workflow.Context) error { + logger := workflow.GetLogger(ctx) + logger.Info("QueryWorkflow started") + + queryResult := "started" + + // Register query handler for "state" query type + err := workflow.SetQueryHandler(ctx, "state", func(input []byte) (string, error) { + return queryResult, nil + }) + if err != nil { + logger.Info("SetQueryHandler failed: " + err.Error()) + return err + } + + // Update state and wait on timer + queryResult = "waiting on timer" + logger.Info("State changed to: waiting on timer") + + // Wait for 2 minutes (query the workflow while it's waiting!) + workflow.NewTimer(ctx, time.Minute*2).Get(ctx, nil) + logger.Info("Timer fired") + + queryResult = "done" + logger.Info("QueryWorkflow completed") + return nil +} + diff --git a/new_samples/query/worker.go b/new_samples/query/worker.go new file mode 100644 index 0000000..88b65b6 --- /dev/null +++ b/new_samples/query/worker.go @@ -0,0 +1,99 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +// Package worker implements a Cadence worker with basic configurations. +package main + +import ( + "github.com/uber-go/tally" + apiv1 "github.com/uber/cadence-idl/go/proto/api/v1" + "go.uber.org/cadence/.gen/go/cadence/workflowserviceclient" + "go.uber.org/cadence/compatibility" + "go.uber.org/cadence/worker" + "go.uber.org/cadence/workflow" + "go.uber.org/yarpc" + "go.uber.org/yarpc/peer" + yarpchostport "go.uber.org/yarpc/peer/hostport" + "go.uber.org/yarpc/transport/grpc" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +const ( + HostPort = "127.0.0.1:7833" + Domain = "cadence-samples" + // TaskListName identifies set of client workflows, activities, and workers. + // It could be your group or client or application name. + TaskListName = "cadence-samples-worker" + ClientName = "cadence-samples-worker" + CadenceService = "cadence-frontend" +) + +// StartWorker creates and starts a basic Cadence worker. +func StartWorker() { + logger, cadenceClient := BuildLogger(), BuildCadenceClient() + workerOptions := worker.Options{ + Logger: logger, + MetricsScope: tally.NewTestScope(TaskListName, nil), + } + + w := worker.New( + cadenceClient, + Domain, + TaskListName, + workerOptions) + // HelloWorld workflow registration + w.RegisterWorkflowWithOptions(QueryWorkflow, workflow.RegisterOptions{Name: "cadence_samples.QueryWorkflow"}) + + err := w.Start() + if err != nil { + panic("Failed to start worker: " + err.Error()) + } + logger.Info("Started Worker.", zap.String("worker", TaskListName)) + +} + +func BuildCadenceClient(dialOptions ...grpc.DialOption) workflowserviceclient.Interface { + grpcTransport := grpc.NewTransport() + // Create a single peer chooser that identifies the host/port and configures + // a gRPC dialer with TLS credentials + myChooser := peer.NewSingle( + yarpchostport.Identify(HostPort), + grpcTransport.NewDialer(dialOptions...), + ) + outbound := grpcTransport.NewOutbound(myChooser) + + dispatcher := yarpc.NewDispatcher(yarpc.Config{ + Name: ClientName, + Outbounds: yarpc.Outbounds{ + CadenceService: {Unary: outbound}, + }, + }) + if err := dispatcher.Start(); err != nil { + panic("Failed to start dispatcher: " + err.Error()) + } + + clientConfig := dispatcher.ClientConfig(CadenceService) + + // Create a compatibility adapter that wraps proto-based YARPC clients + // to provide a unified interface for domain, workflow, worker, and visibility APIs + return compatibility.NewThrift2ProtoAdapter( + apiv1.NewDomainAPIYARPCClient(clientConfig), + apiv1.NewWorkflowAPIYARPCClient(clientConfig), + apiv1.NewWorkerAPIYARPCClient(clientConfig), + apiv1.NewVisibilityAPIYARPCClient(clientConfig), + ) +} + +func BuildLogger() *zap.Logger { + config := zap.NewDevelopmentConfig() + config.Level.SetLevel(zapcore.InfoLevel) + + var err error + logger, err := config.Build() + if err != nil { + panic("Failed to setup logger: " + err.Error()) + } + + return logger +} diff --git a/new_samples/retryactivity/generator/README.md b/new_samples/retryactivity/generator/README.md new file mode 100644 index 0000000..1da3502 --- /dev/null +++ b/new_samples/retryactivity/generator/README.md @@ -0,0 +1,23 @@ + + + +# Sample Generator + +This folder is NOT part of the actual sample. It exists only for contributors who work on this sample. Please disregard it if you are trying to learn about Cadence. + +To create a better learning experience for Cadence users, each sample folder is designed to be self contained. Users can view every part of writing and running workflows, including: + +* Cadence client initialization +* Worker with workflow and activity registrations +* Workflow starter +* and the workflow code itself + +Some samples may have more or fewer parts depending on what they need to demonstrate. + +In most cases, the workflow code (e.g. `workflow.go`) is the part that users care about. The rest is boilerplate needed to run that workflow. For each sample folder, the workflow code should be written by hand. The boilerplate can be generated. Keeping all parts inside one folder gives early learners more value because they can see everything together rather than jumping across directories. + +## Contributing + +* When creating a new sample, follow the steps mentioned in the README file in the main samples folder. +* To update the sample workflow code, edit the workflow file directly. +* To update the worker, client, or other boilerplate logic, edit the generator file. If your change applies to all samples, update the common generator file inside the `template` folder. Edit the generator file in this folder only when the change should affect this sample alone. diff --git a/new_samples/retryactivity/generator/README_specific.md b/new_samples/retryactivity/generator/README_specific.md new file mode 100644 index 0000000..9769005 --- /dev/null +++ b/new_samples/retryactivity/generator/README_specific.md @@ -0,0 +1,55 @@ +## Retry Activity Sample + +This sample demonstrates **retry policies** and **heartbeat progress tracking** for unreliable activities. + +### Start the Workflow + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow start \ + --tl cadence-samples-worker \ + --et 600 \ + --workflow_type cadence_samples.RetryWorkflow +``` + +### What Happens + +The activity processes 20 tasks but intentionally fails after every ~7 tasks (1/3 of batch). With retry policy, it resumes from the last heartbeated progress. + +``` +Attempt 1: Process tasks 0-6, fail, heartbeat progress=6 +Attempt 2: Resume from 7, process 7-13, fail, heartbeat progress=13 +Attempt 3: Resume from 14, process 14-19, complete! +``` + +### Key Concept: Retry Policy + +```go +ao := workflow.ActivityOptions{ + RetryPolicy: &cadence.RetryPolicy{ + InitialInterval: time.Second, // First retry after 1s + BackoffCoefficient: 2.0, // Double wait each retry + MaximumInterval: time.Minute, // Cap at 1 minute + MaximumAttempts: 5, // Give up after 5 tries + NonRetriableErrorReasons: []string{"bad-error"}, // Don't retry these + }, +} +``` + +### Key Concept: Heartbeat with Progress + +```go +// Record progress during activity execution +activity.RecordHeartbeat(ctx, currentTaskID) + +// On retry, resume from last heartbeated progress +if activity.HasHeartbeatDetails(ctx) { + var lastCompletedID int + activity.GetHeartbeatDetails(ctx, &lastCompletedID) + startFrom = lastCompletedID + 1 +} +``` + +This avoids reprocessing already-completed work after a failure. + diff --git a/new_samples/retryactivity/generator/generate.go b/new_samples/retryactivity/generator/generate.go new file mode 100644 index 0000000..20deae2 --- /dev/null +++ b/new_samples/retryactivity/generator/generate.go @@ -0,0 +1,14 @@ +package main + +import "github.com/uber-common/cadence-samples/new_samples/template" + +func main() { + data := template.TemplateData{ + SampleName: "Retry Activity", + Workflows: []string{"RetryWorkflow"}, + Activities: []string{"BatchProcessingActivity"}, + } + + template.GenerateAll(data) +} + diff --git a/new_samples/retryactivity/main.go b/new_samples/retryactivity/main.go new file mode 100644 index 0000000..5893999 --- /dev/null +++ b/new_samples/retryactivity/main.go @@ -0,0 +1,20 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" +) + +func main() { + StartWorker() + + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT) + fmt.Println("Cadence worker started, press ctrl+c to terminate...") + <-done +} diff --git a/new_samples/retryactivity/retryactivity.go b/new_samples/retryactivity/retryactivity.go new file mode 100644 index 0000000..255eb6a --- /dev/null +++ b/new_samples/retryactivity/retryactivity.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "time" + + "go.uber.org/cadence" + "go.uber.org/cadence/activity" + "go.uber.org/cadence/workflow" + "go.uber.org/zap" +) + +// RetryWorkflow demonstrates retry policies for unreliable activities. +// The activity will fail and retry, resuming from heartbeated progress. +func RetryWorkflow(ctx workflow.Context) error { + logger := workflow.GetLogger(ctx) + logger.Info("RetryWorkflow started") + + ao := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute * 10, + HeartbeatTimeout: time.Second * 10, + RetryPolicy: &cadence.RetryPolicy{ + InitialInterval: time.Second, + BackoffCoefficient: 2.0, + MaximumInterval: time.Minute, + ExpirationInterval: time.Minute * 5, + MaximumAttempts: 5, + NonRetriableErrorReasons: []string{"bad-error"}, + }, + } + ctx = workflow.WithActivityOptions(ctx, ao) + + err := workflow.ExecuteActivity(ctx, BatchProcessingActivity, 0, 20, time.Second).Get(ctx, nil) + if err != nil { + logger.Error("RetryWorkflow failed", zap.Error(err)) + return err + } + + logger.Info("RetryWorkflow completed successfully") + return nil +} + +// BatchProcessingActivity processes tasks and intentionally fails to demonstrate retry. +// It heartbeats progress so retries can resume from where they left off. +func BatchProcessingActivity(ctx context.Context, firstTaskID, batchSize int, processDelay time.Duration) error { + logger := activity.GetLogger(ctx) + + startFrom := firstTaskID + + // Check if we're retrying and have previous progress + if activity.HasHeartbeatDetails(ctx) { + var lastCompletedID int + if err := activity.GetHeartbeatDetails(ctx, &lastCompletedID); err == nil { + startFrom = lastCompletedID + 1 + logger.Info("Resuming from previous attempt", zap.Int("startFrom", startFrom)) + } + } + + tasksInThisAttempt := 0 + for i := startFrom; i < firstTaskID+batchSize; i++ { + logger.Info("Processing task", zap.Int("taskID", i)) + time.Sleep(processDelay) + + // Record progress + activity.RecordHeartbeat(ctx, i) + tasksInThisAttempt++ + + // Simulate failure after processing 1/3 of tasks (but not on last task) + if tasksInThisAttempt >= batchSize/3 && i < firstTaskID+batchSize-1 { + logger.Info("Simulating failure - will retry from heartbeated progress") + return cadence.NewCustomError("some-retryable-error") + } + } + + logger.Info("BatchProcessingActivity completed all tasks") + return nil +} + diff --git a/new_samples/retryactivity/worker.go b/new_samples/retryactivity/worker.go new file mode 100644 index 0000000..4da825c --- /dev/null +++ b/new_samples/retryactivity/worker.go @@ -0,0 +1,101 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +// Package worker implements a Cadence worker with basic configurations. +package main + +import ( + "github.com/uber-go/tally" + apiv1 "github.com/uber/cadence-idl/go/proto/api/v1" + "go.uber.org/cadence/.gen/go/cadence/workflowserviceclient" + "go.uber.org/cadence/activity" + "go.uber.org/cadence/compatibility" + "go.uber.org/cadence/worker" + "go.uber.org/cadence/workflow" + "go.uber.org/yarpc" + "go.uber.org/yarpc/peer" + yarpchostport "go.uber.org/yarpc/peer/hostport" + "go.uber.org/yarpc/transport/grpc" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +const ( + HostPort = "127.0.0.1:7833" + Domain = "cadence-samples" + // TaskListName identifies set of client workflows, activities, and workers. + // It could be your group or client or application name. + TaskListName = "cadence-samples-worker" + ClientName = "cadence-samples-worker" + CadenceService = "cadence-frontend" +) + +// StartWorker creates and starts a basic Cadence worker. +func StartWorker() { + logger, cadenceClient := BuildLogger(), BuildCadenceClient() + workerOptions := worker.Options{ + Logger: logger, + MetricsScope: tally.NewTestScope(TaskListName, nil), + } + + w := worker.New( + cadenceClient, + Domain, + TaskListName, + workerOptions) + // HelloWorld workflow registration + w.RegisterWorkflowWithOptions(RetryWorkflow, workflow.RegisterOptions{Name: "cadence_samples.RetryWorkflow"}) + w.RegisterActivityWithOptions(BatchProcessingActivity, activity.RegisterOptions{Name: "cadence_samples.BatchProcessingActivity"}) + + err := w.Start() + if err != nil { + panic("Failed to start worker: " + err.Error()) + } + logger.Info("Started Worker.", zap.String("worker", TaskListName)) + +} + +func BuildCadenceClient(dialOptions ...grpc.DialOption) workflowserviceclient.Interface { + grpcTransport := grpc.NewTransport() + // Create a single peer chooser that identifies the host/port and configures + // a gRPC dialer with TLS credentials + myChooser := peer.NewSingle( + yarpchostport.Identify(HostPort), + grpcTransport.NewDialer(dialOptions...), + ) + outbound := grpcTransport.NewOutbound(myChooser) + + dispatcher := yarpc.NewDispatcher(yarpc.Config{ + Name: ClientName, + Outbounds: yarpc.Outbounds{ + CadenceService: {Unary: outbound}, + }, + }) + if err := dispatcher.Start(); err != nil { + panic("Failed to start dispatcher: " + err.Error()) + } + + clientConfig := dispatcher.ClientConfig(CadenceService) + + // Create a compatibility adapter that wraps proto-based YARPC clients + // to provide a unified interface for domain, workflow, worker, and visibility APIs + return compatibility.NewThrift2ProtoAdapter( + apiv1.NewDomainAPIYARPCClient(clientConfig), + apiv1.NewWorkflowAPIYARPCClient(clientConfig), + apiv1.NewWorkerAPIYARPCClient(clientConfig), + apiv1.NewVisibilityAPIYARPCClient(clientConfig), + ) +} + +func BuildLogger() *zap.Logger { + config := zap.NewDevelopmentConfig() + config.Level.SetLevel(zapcore.InfoLevel) + + var err error + logger, err := config.Build() + if err != nil { + panic("Failed to setup logger: " + err.Error()) + } + + return logger +} diff --git a/new_samples/sleep/generator/README.md b/new_samples/sleep/generator/README.md new file mode 100644 index 0000000..1da3502 --- /dev/null +++ b/new_samples/sleep/generator/README.md @@ -0,0 +1,23 @@ + + + +# Sample Generator + +This folder is NOT part of the actual sample. It exists only for contributors who work on this sample. Please disregard it if you are trying to learn about Cadence. + +To create a better learning experience for Cadence users, each sample folder is designed to be self contained. Users can view every part of writing and running workflows, including: + +* Cadence client initialization +* Worker with workflow and activity registrations +* Workflow starter +* and the workflow code itself + +Some samples may have more or fewer parts depending on what they need to demonstrate. + +In most cases, the workflow code (e.g. `workflow.go`) is the part that users care about. The rest is boilerplate needed to run that workflow. For each sample folder, the workflow code should be written by hand. The boilerplate can be generated. Keeping all parts inside one folder gives early learners more value because they can see everything together rather than jumping across directories. + +## Contributing + +* When creating a new sample, follow the steps mentioned in the README file in the main samples folder. +* To update the sample workflow code, edit the workflow file directly. +* To update the worker, client, or other boilerplate logic, edit the generator file. If your change applies to all samples, update the common generator file inside the `template` folder. Edit the generator file in this folder only when the change should affect this sample alone. diff --git a/new_samples/sleep/generator/README_specific.md b/new_samples/sleep/generator/README_specific.md new file mode 100644 index 0000000..c809e24 --- /dev/null +++ b/new_samples/sleep/generator/README_specific.md @@ -0,0 +1,57 @@ +## Sleep Workflow Sample + +This sample demonstrates **workflow.Sleep** - pausing workflow execution for a specified duration. + +### Start the Workflow + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow start \ + --tl cadence-samples-worker \ + --et 120 \ + --workflow_type cadence_samples.SleepWorkflow \ + --input '10000000000' +``` + +The input is sleep duration in nanoseconds (10 seconds = 10000000000). + +### What Happens + +``` +Time 0: Workflow starts + └── workflow.Sleep(10s) begins + +Time 10s: Sleep completes + └── MainSleepActivity executes + +Time ~10s: Workflow completes +``` + +### Key Concept: workflow.Sleep + +```go +func SleepWorkflow(ctx workflow.Context, sleepDuration time.Duration) error { + // Sleep for the specified duration + err := workflow.Sleep(ctx, sleepDuration) + if err != nil { + return err + } + + // Continue with workflow logic after sleep + return workflow.ExecuteActivity(ctx, MainSleepActivity).Get(ctx, nil) +} +``` + +### Sleep vs Timer + +- `workflow.Sleep(ctx, duration)` - Simple, blocks workflow execution +- `workflow.NewTimer(ctx, duration)` - Returns a Future, can be used with Selector for racing + +### Use Cases + +- Delayed processing +- Rate limiting +- Scheduled tasks +- Waiting periods between retries + diff --git a/new_samples/sleep/generator/generate.go b/new_samples/sleep/generator/generate.go new file mode 100644 index 0000000..1cf8856 --- /dev/null +++ b/new_samples/sleep/generator/generate.go @@ -0,0 +1,14 @@ +package main + +import "github.com/uber-common/cadence-samples/new_samples/template" + +func main() { + data := template.TemplateData{ + SampleName: "Sleep", + Workflows: []string{"SleepWorkflow"}, + Activities: []string{"MainSleepActivity"}, + } + + template.GenerateAll(data) +} + diff --git a/new_samples/sleep/main.go b/new_samples/sleep/main.go new file mode 100644 index 0000000..5893999 --- /dev/null +++ b/new_samples/sleep/main.go @@ -0,0 +1,20 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" +) + +func main() { + StartWorker() + + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT) + fmt.Println("Cadence worker started, press ctrl+c to terminate...") + <-done +} diff --git a/new_samples/sleep/sleep.go b/new_samples/sleep/sleep.go new file mode 100644 index 0000000..c342753 --- /dev/null +++ b/new_samples/sleep/sleep.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "time" + + "go.uber.org/cadence/activity" + "go.uber.org/cadence/workflow" + "go.uber.org/zap" +) + +// SleepWorkflow demonstrates workflow.Sleep for pausing execution. +func SleepWorkflow(ctx workflow.Context, sleepDuration time.Duration) error { + logger := workflow.GetLogger(ctx) + logger.Info("SleepWorkflow started", zap.Duration("sleepDuration", sleepDuration)) + + // Sleep for the specified duration + err := workflow.Sleep(ctx, sleepDuration) + if err != nil { + logger.Error("Sleep failed", zap.Error(err)) + return err + } + + logger.Info("Sleep finished, executing activity") + + // Set activity options + ao := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + } + ctx = workflow.WithActivityOptions(ctx, ao) + + var result string + err = workflow.ExecuteActivity(ctx, MainSleepActivity).Get(ctx, &result) + if err != nil { + logger.Error("Activity failed", zap.Error(err)) + return err + } + + logger.Info("SleepWorkflow completed", zap.String("result", result)) + return nil +} + +// MainSleepActivity is executed after the sleep completes. +func MainSleepActivity(ctx context.Context) (string, error) { + logger := activity.GetLogger(ctx) + logger.Info("MainSleepActivity executed") + return "Activity completed after sleep", nil +} + diff --git a/new_samples/sleep/worker.go b/new_samples/sleep/worker.go new file mode 100644 index 0000000..4a89f4b --- /dev/null +++ b/new_samples/sleep/worker.go @@ -0,0 +1,101 @@ +// THIS IS A GENERATED FILE +// PLEASE DO NOT EDIT + +// Package worker implements a Cadence worker with basic configurations. +package main + +import ( + "github.com/uber-go/tally" + apiv1 "github.com/uber/cadence-idl/go/proto/api/v1" + "go.uber.org/cadence/.gen/go/cadence/workflowserviceclient" + "go.uber.org/cadence/activity" + "go.uber.org/cadence/compatibility" + "go.uber.org/cadence/worker" + "go.uber.org/cadence/workflow" + "go.uber.org/yarpc" + "go.uber.org/yarpc/peer" + yarpchostport "go.uber.org/yarpc/peer/hostport" + "go.uber.org/yarpc/transport/grpc" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +const ( + HostPort = "127.0.0.1:7833" + Domain = "cadence-samples" + // TaskListName identifies set of client workflows, activities, and workers. + // It could be your group or client or application name. + TaskListName = "cadence-samples-worker" + ClientName = "cadence-samples-worker" + CadenceService = "cadence-frontend" +) + +// StartWorker creates and starts a basic Cadence worker. +func StartWorker() { + logger, cadenceClient := BuildLogger(), BuildCadenceClient() + workerOptions := worker.Options{ + Logger: logger, + MetricsScope: tally.NewTestScope(TaskListName, nil), + } + + w := worker.New( + cadenceClient, + Domain, + TaskListName, + workerOptions) + // HelloWorld workflow registration + w.RegisterWorkflowWithOptions(SleepWorkflow, workflow.RegisterOptions{Name: "cadence_samples.SleepWorkflow"}) + w.RegisterActivityWithOptions(MainSleepActivity, activity.RegisterOptions{Name: "cadence_samples.MainSleepActivity"}) + + err := w.Start() + if err != nil { + panic("Failed to start worker: " + err.Error()) + } + logger.Info("Started Worker.", zap.String("worker", TaskListName)) + +} + +func BuildCadenceClient(dialOptions ...grpc.DialOption) workflowserviceclient.Interface { + grpcTransport := grpc.NewTransport() + // Create a single peer chooser that identifies the host/port and configures + // a gRPC dialer with TLS credentials + myChooser := peer.NewSingle( + yarpchostport.Identify(HostPort), + grpcTransport.NewDialer(dialOptions...), + ) + outbound := grpcTransport.NewOutbound(myChooser) + + dispatcher := yarpc.NewDispatcher(yarpc.Config{ + Name: ClientName, + Outbounds: yarpc.Outbounds{ + CadenceService: {Unary: outbound}, + }, + }) + if err := dispatcher.Start(); err != nil { + panic("Failed to start dispatcher: " + err.Error()) + } + + clientConfig := dispatcher.ClientConfig(CadenceService) + + // Create a compatibility adapter that wraps proto-based YARPC clients + // to provide a unified interface for domain, workflow, worker, and visibility APIs + return compatibility.NewThrift2ProtoAdapter( + apiv1.NewDomainAPIYARPCClient(clientConfig), + apiv1.NewWorkflowAPIYARPCClient(clientConfig), + apiv1.NewWorkerAPIYARPCClient(clientConfig), + apiv1.NewVisibilityAPIYARPCClient(clientConfig), + ) +} + +func BuildLogger() *zap.Logger { + config := zap.NewDevelopmentConfig() + config.Level.SetLevel(zapcore.InfoLevel) + + var err error + logger, err := config.Build() + if err != nil { + panic("Failed to setup logger: " + err.Error()) + } + + return logger +}