diff --git a/new_samples/signal/README.md b/new_samples/signal/README.md new file mode 100644 index 0000000..5036970 --- /dev/null +++ b/new_samples/signal/README.md @@ -0,0 +1,68 @@ + + + +# Signal Workflow Sample + +## Prerequisites + +0. Install Cadence CLI. See instruction [here](https://cadenceworkflow.io/docs/cli/). +1. Run the Cadence server: + 1. Clone the [Cadence](https://github.com/cadence-workflow/cadence) repository if you haven't done already: `git clone https://github.com/cadence-workflow/cadence.git` + 2. Run `docker compose -f docker/docker-compose.yml up` to start Cadence server + 3. See more details at https://github.com/uber/cadence/blob/master/README.md +2. Once everything is up and running in Docker, open [localhost:8088](localhost:8088) to view Cadence UI. +3. Register the `cadence-samples` domain: + +```bash +cadence --env development --domain cadence-samples domain register +``` + +Refresh the [domains page](http://localhost:8088/domains) from step 2 to verify `cadence-samples` is registered. + +## Steps to run sample + +Inside the folder this sample is defined, run the following command: + +```bash +go run . +``` + +This will call the main function in main.go which starts the worker, which will be execute the sample workflow code + +## Simple Signal Workflow + +This workflow takes an input message and greet you as response. Try the following CLI + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow start \ + --tl cadence-samples-worker \ + --et 60 \ + --workflow_type cadence_samples.SimpleSignalWorkflow +``` + +Verify that your workflow started. Your can find your worklow by looking at the "Workflow type" column. + +If this is your first sample, please refer to [HelloWorkflow sample](https://github.com/cadence-workflow/cadence-samples/tree/master/new_samples/hello_world) about how to view your workflows. + + +### Signal your workflow + +This workflow will need a signal to complete successfully. Below is how you can send a signal. In this example, we are sending a `bool` value `true` (JSON formatted) via the signal called `complete` + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow signal \ + --wid \ + --name complete \ + --input 'true' +``` + +## References + +* The website: https://cadenceworkflow.io +* Cadence's server: https://github.com/uber/cadence +* Cadence's Go client: https://github.com/uber-go/cadence-client + diff --git a/new_samples/signal/generator/README.md b/new_samples/signal/generator/README.md new file mode 100644 index 0000000..1da3502 --- /dev/null +++ b/new_samples/signal/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/signal/generator/README_specific.md b/new_samples/signal/generator/README_specific.md new file mode 100644 index 0000000..afc66ad --- /dev/null +++ b/new_samples/signal/generator/README_specific.md @@ -0,0 +1,30 @@ +## Simple Signal Workflow + +This workflow takes an input message and greet you as response. Try the following CLI + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow start \ + --tl cadence-samples-worker \ + --et 60 \ + --workflow_type cadence_samples.SimpleSignalWorkflow +``` + +Verify that your workflow started. Your can find your worklow by looking at the "Workflow type" column. + +If this is your first sample, please refer to [HelloWorkflow sample](https://github.com/cadence-workflow/cadence-samples/tree/master/new_samples/hello_world) about how to view your workflows. + + +### Signal your workflow + +This workflow will need a signal to complete successfully. Below is how you can send a signal. In this example, we are sending a `bool` value `true` (JSON formatted) via the signal called `complete` + +```bash +cadence --env development \ + --domain cadence-samples \ + workflow signal \ + --wid \ + --name complete \ + --input 'true' +``` diff --git a/new_samples/signal/generator/generate.go b/new_samples/signal/generator/generate.go new file mode 100644 index 0000000..fca5329 --- /dev/null +++ b/new_samples/signal/generator/generate.go @@ -0,0 +1,16 @@ +package main + +import "github.com/uber-common/cadence-samples/new_samples/template" + +func main() { + // Define the data for HelloWorld + data := template.TemplateData{ + SampleName: "Signal Workflow", + Workflows: []string{"SimpleSignalWorkflow"}, + Activities: []string{"SimpleSignalActivity"}, + } + + template.GenerateAll(data) +} + +// Implement custom generator below \ No newline at end of file diff --git a/new_samples/signal/main.go b/new_samples/signal/main.go new file mode 100644 index 0000000..5893999 --- /dev/null +++ b/new_samples/signal/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/signal/simple_signal_workflow.go b/new_samples/signal/simple_signal_workflow.go new file mode 100644 index 0000000..125ed5e --- /dev/null +++ b/new_samples/signal/simple_signal_workflow.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "go.uber.org/cadence/workflow" + "go.uber.org/cadence/activity" + "strconv" + "time" + "go.uber.org/zap" +) + +const ( + CompleteSignalChan = "complete" +) + +func SimpleSignalWorkflow(ctx workflow.Context) error { + ao := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute * 60, + StartToCloseTimeout: time.Minute * 60, + } + ctx = workflow.WithActivityOptions(ctx, ao) + logger := workflow.GetLogger(ctx) + logger.Info("SimpleSignalWorkflow started") + + var complete bool + completeChan := workflow.GetSignalChannel(ctx, CompleteSignalChan) + for { + s := workflow.NewSelector(ctx) + s.AddReceive(completeChan, func(ch workflow.Channel, ok bool) { + if ok { + ch.Receive(ctx, &complete) + } + logger.Info("Signal input: " + strconv.FormatBool(complete)) + }) + s.Select(ctx) + + var result string + err := workflow.ExecuteActivity(ctx, SimpleSignalActivity, complete).Get(ctx, &result) + if err != nil { + return err + } + logger.Info("Activity result: " + result) + if complete { + return nil + } + } +} + +func SimpleSignalActivity(ctx context.Context, complete bool) (string, error) { + logger := activity.GetLogger(ctx) + logger.Info("SimpleSignalActivity started, a new signal has been received", zap.Bool("complete", complete)) + if complete { + return "Workflow will complete now", nil + } + return "Workflow will continue to run", nil +} diff --git a/new_samples/signal/worker.go b/new_samples/signal/worker.go new file mode 100644 index 0000000..ee0d8d4 --- /dev/null +++ b/new_samples/signal/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(SimpleSignalWorkflow, workflow.RegisterOptions{Name: "cadence_samples.SimpleSignalWorkflow"}) + w.RegisterActivityWithOptions(SimpleSignalActivity, activity.RegisterOptions{Name: "cadence_samples.SimpleSignalActivity"}) + + 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 +}