From 205698fec22fbaa7540d1a466d2c4d4968c3a591 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 23 Jul 2024 15:24:33 -0400 Subject: [PATCH 1/8] jobs quickstart Signed-off-by: Hannah Hunter --- .../quickstarts/jobs-quickstart.md | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md diff --git a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md new file mode 100644 index 00000000000..c39ef56e22e --- /dev/null +++ b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md @@ -0,0 +1,223 @@ +--- +type: docs +title: "Quickstart: Jobs" +linkTitle: Jobs +weight: 80 +description: Get started with the Dapr jobs building block +--- + +{{% alert title="Alpha" color="warning" %}} +The jobs building block is currently in **alpha**. +{{% /alert %}} + +Let's take a look at the [Dapr jobs building block]({{< ref jobs-overview.md >}}), which schedules and runs jobs at a specific time or interval. In this Quickstart, you'll schedule, get, and delete a job using Dapr's Job API. + +Need diagram + + +You can try out this pub/sub quickstart by either: + +- [Running all applications in this sample simultaneously with the Multi-App Run template file]({{< ref "#run-using-multi-app-run" >}}), or +- [Running one application at a time]({{< ref "#run-one-job-application-at-a-time" >}}) + + +## Run using Multi-App Run + +Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. Currently, you can experiment with the jobs API with the Go SDK. + +{{< tabs Go >}} + + +{{% codetab %}} + +This quickstart includes two apps: + +- **`job-scheduler.go`:** schedules, retrieves, and deletes jobs. +- **`job-service.go`:** handles the scheduled jobs. + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest version of Go](https://go.dev/dl/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/jobs). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstarts directory, navigate into the jobs directory: + +```bash +cd jobs/go/sdk +``` + +```bash +dapr run -f . +``` + +In the terminal output, you can see the following jobs being scheduled, retrieved, and deleted. + +- The `R2-D2` job is being scheduled. +- The `C-3PO` job is being scheduled. +- The `C-3PO` job is being retrieved. +- The `BB-8` job is being scheduled. +- The `BB-8` job is being retrieved. +- The `BB-8` job is being deleted. +- The `R2-D2` job is being executed after 5 seconds. +- The `R2-D2` job is being executed after 10 seconds. + +**Expected output** + +```text +== APP - job-service == dapr client initializing for: 127.0.0.1:6281 +== APP - job-service == Registered job handler for: R2-D2 +== APP - job-service == Registered job handler for: C-3PO +== APP - job-service == Registered job handler for: BB-8 +== APP - job-service == Starting server on port: 6200 +== APP - job-service == Job scheduled: R2-D2 +== APP - job-service == Job scheduled: C-3PO +== APP - job-service == 2024/07/17 18:09:59 job:{name:"C-3PO" due_time:"10s" data:{value:"{\"droid\":\"C-3PO\",\"Task\":\"Memory Wipe\"}"}} +== APP - job-scheduler == Get job response: {"droid":"C-3PO","Task":"Memory Wipe"} +== APP - job-service == Job scheduled: BB-8 +== APP - job-service == 2024/07/17 18:09:59 job:{name:"BB-8" due_time:"15s" data:{value:"{\"droid\":\"BB-8\",\"Task\":\"Internal Gyroscope Check\"}"}} +== APP - job-scheduler == Get job response: {"droid":"BB-8","Task":"Internal Gyroscope Check"} +== APP - job-scheduler == Deleted job: BB-8 +``` + +After 5 seconds, the terminal output should present the `R2-D2` job being processed: + +```text +== APP - job-service == Starting droid: R2-D2 +== APP - job-service == Executing maintenance job: Oil Change +``` + +After 10 seconds, the terminal output should present the `C3-PO` job being processed: + +```text +== APP - job-service == Starting droid: C-3PO +== APP - job-service == Executing maintenance job: Memory Wipe +``` + +Once the process has completed, you can stop and clean up application processes with a single command. + +```bash +dapr stop -f . +``` + +### What happened? + +{{% /codetab %}} + +{{< /tabs >}} + +## Run one job application at a time + +{{< tabs Go >}} + + +{{% codetab %}} + +This quickstart includes two apps: + +- **`job-scheduler.go`:** schedules, retrieves, and deletes jobs. +- **`job-service.go`:** handles the scheduled jobs. + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest version of Go](https://go.dev/dl/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/jobs). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstarts directory, navigate into the jobs directory: + +```bash +cd jobs/go/sdk +``` + +### Step 3: Schedule jobs + +In the terminal, run the `job-service` app: + +```bash +dapr run --app-id job-service --app-port 6200 --dapr-http-port 6280 --dapr-grpc-port 6281 --app-protocol grpc -- go run . +``` + +**Expected output** + +```text +== APP == dapr client initializing for: 127.0.0.1:6281 +== APP == Registered job handler for: R2-D2 +== APP == Registered job handler for: C-3PO +== APP == Registered job handler for: BB-8 +== APP == Starting server on port: 6200 +``` + +In a new terminal window, run the `job-scheduler` app: + +```bash +dapr run --app-id job-scheduler --app-port 6300 -- go run . +``` + +**Expected output** + +```text +== APP == dapr client initializing for: +== APP == Get job response: {"droid":"C-3PO","Task":"Memory Wipe"} +== APP == Get job response: {"droid":"BB-8","Task":"Internal Gyroscope Check"} +== APP == Job deleted: BB-8 +``` + +Return to the `job-service` app terminal window. The output should be: + +```text +== APP == Job scheduled: R2-D2 +== APP == Job scheduled: C-3PO +== APP == 2024/07/17 18:25:36 job:{name:"C-3PO" due_time:"10s" data:{value:"{\"droid\":\"C-3PO\",\"Task\":\"Memory Wipe\"}"}} +== APP == Job scheduled: BB-8 +== APP == 2024/07/17 18:25:36 job:{name:"BB-8" due_time:"15s" data:{value:"{\"droid\":\"BB-8\",\"Task\":\"Internal Gyroscope Check\"}"}} +== APP == Starting droid: R2-D2 +== APP == Executing maintenance job: Oil Change +== APP == Starting droid: C-3PO +== APP == Executing maintenance job: Memory Wipe +``` + +### What happened? + + +{{% /codetab %}} + +{{< /tabs >}} + +## Tell us what you think! + +We're continuously working to improve our Quickstart examples and value your feedback. Did you find this Quickstart helpful? Do you have suggestions for improvement? + +Join the discussion in our [discord channel](https://discord.com/channels/778680217417809931/953427615916638238). + +## Next steps + +- Walk through [more examples of scheduling and orchestrating jobs using the jobs API](link) +- Learn more about [the jobs building block](link) +- Learn more about [the scheduler control plane](link) + +{{< button text="Explore Dapr tutorials >>" page="getting-started/tutorials/_index.md" >}} From c8eef0fde023209c7efceef205099dd7a0861cd7 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 24 Jul 2024 16:28:54 -0400 Subject: [PATCH 2/8] update quickstart doc Signed-off-by: Hannah Hunter --- .../quickstarts/jobs-quickstart.md | 329 +++++++++++++++++- 1 file changed, 323 insertions(+), 6 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md index c39ef56e22e..36f1329106f 100644 --- a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md @@ -114,6 +114,318 @@ dapr stop -f . ### What happened? +When you ran `dapr init` during Dapr install: + +- The `dapr_scheduler` control plane was started alongside other Dapr services. +- [The `dapr.yaml` Multi-App Run template file]({{< ref #dapryaml-multi-app-run-template-filer >}}) was generated in the `.dapr/components` directory. + +Running `dapr run -f .` in this Quickstart started both the `job-scheduler` and the `job-service`. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +apps: + - appDirPath: ./job-service/ + appID: job-service + appPort: 6200 + daprGRPCPort: 6281 + appProtocol: grpc + command: ["go", "run", "."] + - appDirPath: ./job-scheduler/ + appID: job-scheduler + appPort: 6300 + command: ["go", "run", "."] +``` + +#### `job-service` app + +The `job-service` application creates handlers for the job service. + +```go +if err := server.AddServiceInvocationHandler("scheduleJob", scheduleJob); err != nil { + log.Fatalf("error adding invocation handler: %v", err) +} + +if err := server.AddServiceInvocationHandler("getJob", getJob); err != nil { + log.Fatalf("error adding invocation handler: %v", err) +} + +if err := server.AddServiceInvocationHandler("deleteJob", deleteJob); err != nil { + log.Fatalf("error adding invocation handler: %v", err) +} +``` + +Next, the jobs event handler is registered: + +```go +for _, jobName := range jobNames { + if err := server.AddJobEventHandler(jobName, handleJob); err != nil { + log.Fatalf("failed to register job event handler: %v", err) + } + fmt.Println("Registered job handler for: ", jobName) +} + +fmt.Println("Starting server on port: " + appPort) +if err = server.Start(); err != nil { + log.Fatalf("failed to start server: %v", err) +} +``` + +The `job-service` then codes the functions that handle scheduling, getting, and deleting jobs, and handles job events. + +```go +// Handler that schedules a DroidJob +func scheduleJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { + + if in == nil { + err = errors.New("no invocation parameter") + return + } + + droidJob := DroidJob{} + err = json.Unmarshal(in.Data, &droidJob) + if err != nil { + fmt.Println("failed to unmarshal job: ", err) + return nil, err + } + + jobData := JobData{ + Droid: droidJob.Name, + Task: droidJob.Job, + } + + content, err := json.Marshal(jobData) + if err != nil { + fmt.Printf("Error marshalling job content") + return nil, err + } + + // schedule job + job := daprc.Job{ + Name: droidJob.Name, + DueTime: droidJob.DueTime, + Data: &anypb.Any{ + Value: content, + }, + } + + err = app.daprClient.ScheduleJobAlpha1(ctx, &job) + if err != nil { + fmt.Println("failed to schedule job. err: ", err) + return nil, err + } + + fmt.Println("Job scheduled: ", droidJob.Name) + + out = &common.Content{ + Data: in.Data, + ContentType: in.ContentType, + DataTypeURL: in.DataTypeURL, + } + + return out, err + +} + +// Handler that gets a job by name +func getJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { + + if in == nil { + err = errors.New("no invocation parameter") + return nil, err + } + + job, err := app.daprClient.GetJobAlpha1(ctx, string(in.Data)) + if err != nil { + fmt.Println("failed to get job. err: ", err) + } + + out = &common.Content{ + Data: job.Data.Value, + ContentType: in.ContentType, + DataTypeURL: in.DataTypeURL, + } + + return out, err +} + +// Handler that deletes a job by name +func deleteJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { + if in == nil { + err = errors.New("no invocation parameter") + return nil, err + } + + err = app.daprClient.DeleteJobAlpha1(ctx, string(in.Data)) + if err != nil { + fmt.Println("failed to delete job. err: ", err) + } + + out = &common.Content{ + Data: in.Data, + ContentType: in.ContentType, + DataTypeURL: in.DataTypeURL, + } + + return out, err +} + +// Handler that handles job events +func handleJob(ctx context.Context, job *common.JobEvent) error { + var jobData common.Job + if err := json.Unmarshal(job.Data, &jobData); err != nil { + return fmt.Errorf("failed to unmarshal job: %v", err) + } + decodedPayload, err := base64.StdEncoding.DecodeString(jobData.Value) + if err != nil { + return fmt.Errorf("failed to decode job payload: %v", err) + } + var jobPayload JobData + if err := json.Unmarshal(decodedPayload, &jobPayload); err != nil { + return fmt.Errorf("failed to unmarshal payload: %v", err) + } + + fmt.Println("Starting droid:", jobPayload.Droid) + fmt.Println("Executing maintenance job:", jobPayload.Task) + + return nil +} +``` + +#### `job-scheduler` app + +In the `job-scheduler` application, the R2D2, C3PO, and BB8 jobs are first defined as `[]DroidJob`: + +```go +droidJobs := []DroidJob{ + {Name: "R2-D2", Job: "Oil Change", DueTime: "5s"}, + {Name: "C-3PO", Job: "Memory Wipe", DueTime: "15s"}, + {Name: "BB-8", Job: "Internal Gyroscope Check", DueTime: "30s"}, +} +``` + + +The jobs are then scheduled, retrieved, and deleted using the jobs API. As you can see from the terminal output, first the R2D2 job is scheduled: + +```go +// Schedule R2D2 job +err = schedule(droidJobs[0]) +if err != nil { + log.Fatalln("Error scheduling job: ", err) +} + +time.Sleep(3 * time.Second) +``` + +Then, the C3PO job is scheduled, and returns job data: + +```go +// Schedule C-3PO job +err = schedule(droidJobs[1]) +if err != nil { + log.Fatalln("Error scheduling job: ", err) +} +time.Sleep(5 * time.Second) + +// Get C-3PO job +resp, err := get(droidJobs[1]) +if err != nil { + log.Fatalln("Error retrieving job: ", err) +} +fmt.Println("Get job response: ", resp) +``` + +The BB8 job is then scheduled, retrieved, and deleted: + +```go +// Schedule BB-8 job +err = schedule(droidJobs[2]) +if err != nil { + log.Fatalln("Error scheduling job: ", err) +} + +time.Sleep(5 * time.Second) + +// Get BB-8 job +resp, err = get(droidJobs[2]) +if err != nil { + log.Fatalln("Error retrieving job: ", err) +} +fmt.Println("Get job response: ", resp) + +time.Sleep(5 * time.Second) + +// Delete BB-8 job +err = delete(droidJobs[2]) +if err != nil { + log.Fatalln("Error deleting job: ", err) +} +fmt.Println("Job deleted: ", droidJobs[2].Name) +``` + +The `job-scheduler.go` also defines the `schedule`, `get`, and `delete` functions, calling from `job-service.go`. + +```go +// Schedules a job by invoking grpc service from job-service passing a DroidJob as an argument +func schedule(droidJob DroidJob) error { + jobData, err := json.Marshal(droidJob) + if err != nil { + fmt.Println("Error marshalling job content") + return err + } + + content := &daprc.DataContent{ + ContentType: "application/json", + Data: []byte(jobData), + } + + // Schedule Job + _, err = app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "scheduleJob", "POST", content) + if err != nil { + fmt.Println("Error invoking method: ", err) + return err + } + + return nil +} + +// Gets a job by invoking grpc service from job-service passing a job name as an argument +func get(droidJob DroidJob) (string, error) { + content := &daprc.DataContent{ + ContentType: "text/plain", + Data: []byte(droidJob.Name), + } + + //get job + resp, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "getJob", "GET", content) + if err != nil { + fmt.Println("Error invoking method: ", err) + return "", err + } + + return string(resp), nil +} + +// Deletes a job by invoking grpc service from job-service passing a job name as an argument +func delete(droidJob DroidJob) error { + content := &daprc.DataContent{ + ContentType: "text/plain", + Data: []byte(droidJob.Name), + } + + _, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "deleteJob", "DELETE", content) + if err != nil { + fmt.Println("Error invoking method: ", err) + return err + } + + return nil +} +``` + {{% /codetab %}} {{< /tabs >}} @@ -201,13 +513,17 @@ Return to the `job-service` app terminal window. The output should be: == APP == Executing maintenance job: Memory Wipe ``` -### What happened? - - {{% /codetab %}} {{< /tabs >}} + +## Watch the demo + +See the jobs API in action using a Go HTTP example, recorded during the [Dapr Community Call #107(https://www.youtube.com/live/WHGOc7Ec_YQ?si=JlOlcJKkhRuhf5R1&t=849)]. + + + ## Tell us what you think! We're continuously working to improve our Quickstart examples and value your feedback. Did you find this Quickstart helpful? Do you have suggestions for improvement? @@ -216,8 +532,9 @@ Join the discussion in our [discord channel](https://discord.com/channels/778680 ## Next steps -- Walk through [more examples of scheduling and orchestrating jobs using the jobs API](link) -- Learn more about [the jobs building block](link) -- Learn more about [the scheduler control plane](link) +- HTTP samples of this quickstart: + - [Go](todo) +- Learn more about [the jobs building block]({{< ref jobs-overview.md >}}) +- Learn more about [the scheduler control plane]({{< ref scheduler.md >}}) {{< button text="Explore Dapr tutorials >>" page="getting-started/tutorials/_index.md" >}} From 223f56e9a1b191b0993ba75d87d77e850e517248 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 24 Jul 2024 16:30:04 -0400 Subject: [PATCH 3/8] fix link Signed-off-by: Hannah Hunter --- .../content/en/getting-started/quickstarts/jobs-quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md index 36f1329106f..4dfcbe12dc5 100644 --- a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md @@ -117,7 +117,7 @@ dapr stop -f . When you ran `dapr init` during Dapr install: - The `dapr_scheduler` control plane was started alongside other Dapr services. -- [The `dapr.yaml` Multi-App Run template file]({{< ref #dapryaml-multi-app-run-template-filer >}}) was generated in the `.dapr/components` directory. +- [The `dapr.yaml` Multi-App Run template file]({{< ref "#dapryaml-multi-app-run-template-file" >}}) was generated in the `.dapr/components` directory. Running `dapr run -f .` in this Quickstart started both the `job-scheduler` and the `job-service`. From cfcca3942ba05ef97f35fcc86505024afe340bc7 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 24 Jul 2024 16:33:02 -0400 Subject: [PATCH 4/8] edits Signed-off-by: Hannah Hunter --- .../quickstarts/jobs-quickstart.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md index 4dfcbe12dc5..17a6409b68f 100644 --- a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md @@ -59,21 +59,12 @@ From the root of the Quickstarts directory, navigate into the jobs directory: cd jobs/go/sdk ``` +Run the quickstart with one command: + ```bash dapr run -f . ``` -In the terminal output, you can see the following jobs being scheduled, retrieved, and deleted. - -- The `R2-D2` job is being scheduled. -- The `C-3PO` job is being scheduled. -- The `C-3PO` job is being retrieved. -- The `BB-8` job is being scheduled. -- The `BB-8` job is being retrieved. -- The `BB-8` job is being deleted. -- The `R2-D2` job is being executed after 5 seconds. -- The `R2-D2` job is being executed after 10 seconds. - **Expected output** ```text @@ -119,7 +110,16 @@ When you ran `dapr init` during Dapr install: - The `dapr_scheduler` control plane was started alongside other Dapr services. - [The `dapr.yaml` Multi-App Run template file]({{< ref "#dapryaml-multi-app-run-template-file" >}}) was generated in the `.dapr/components` directory. -Running `dapr run -f .` in this Quickstart started both the `job-scheduler` and the `job-service`. +Running `dapr run -f .` in this Quickstart started both the `job-scheduler` and the `job-service`. In the terminal output, you can see the following jobs being scheduled, retrieved, and deleted. + +- The `R2-D2` job is being scheduled. +- The `C-3PO` job is being scheduled. +- The `C-3PO` job is being retrieved. +- The `BB-8` job is being scheduled. +- The `BB-8` job is being retrieved. +- The `BB-8` job is being deleted. +- The `R2-D2` job is being executed after 5 seconds. +- The `R2-D2` job is being executed after 10 seconds. #### `dapr.yaml` Multi-App Run template file From ee5407122af9af88b1af58983a4cb2203bbce85b Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 24 Jul 2024 16:36:49 -0400 Subject: [PATCH 5/8] header Signed-off-by: Hannah Hunter --- .../content/en/getting-started/quickstarts/jobs-quickstart.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md index 17a6409b68f..d9c1cb3c49e 100644 --- a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md @@ -59,7 +59,9 @@ From the root of the Quickstarts directory, navigate into the jobs directory: cd jobs/go/sdk ``` -Run the quickstart with one command: +### Step 3: Schedule jobs + +Run the application and schedule jobs with one command: ```bash dapr run -f . From a00e043ca1a4a4c4b7bf12233b6322cf31c06ad2 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 29 Jul 2024 11:40:12 -0400 Subject: [PATCH 6/8] update per fernando and mark review Signed-off-by: Hannah Hunter --- .../quickstarts/jobs-quickstart.md | 318 +++++++++++++++++- 1 file changed, 301 insertions(+), 17 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md index d9c1cb3c49e..9be5deae8c1 100644 --- a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md @@ -12,15 +12,11 @@ The jobs building block is currently in **alpha**. Let's take a look at the [Dapr jobs building block]({{< ref jobs-overview.md >}}), which schedules and runs jobs at a specific time or interval. In this Quickstart, you'll schedule, get, and delete a job using Dapr's Job API. -Need diagram - - -You can try out this pub/sub quickstart by either: +You can try out this jobs quickstart by either: - [Running all applications in this sample simultaneously with the Multi-App Run template file]({{< ref "#run-using-multi-app-run" >}}), or - [Running one application at a time]({{< ref "#run-one-job-application-at-a-time" >}}) - ## Run using Multi-App Run Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. Currently, you can experiment with the jobs API with the Go SDK. @@ -144,7 +140,7 @@ apps: #### `job-service` app -The `job-service` application creates handlers for the job service. +The `job-service` application creates service invocation handlers to manage the lifecycle of the job (`scheduleJob`, `getJob`, and `deleteJob`). ```go if err := server.AddServiceInvocationHandler("scheduleJob", scheduleJob); err != nil { @@ -160,7 +156,7 @@ if err := server.AddServiceInvocationHandler("deleteJob", deleteJob); err != nil } ``` -Next, the jobs event handler is registered: +Next, job event handlers are registered for all droids: ```go for _, jobName := range jobNames { @@ -176,7 +172,7 @@ if err = server.Start(); err != nil { } ``` -The `job-service` then codes the functions that handle scheduling, getting, and deleting jobs, and handles job events. +The `job-service` then codes the functions that handle scheduling, getting, deleting, and handling job events. ```go // Handler that schedules a DroidJob @@ -318,8 +314,6 @@ err = schedule(droidJobs[0]) if err != nil { log.Fatalln("Error scheduling job: ", err) } - -time.Sleep(3 * time.Second) ``` Then, the C3PO job is scheduled, and returns job data: @@ -330,7 +324,6 @@ err = schedule(droidJobs[1]) if err != nil { log.Fatalln("Error scheduling job: ", err) } -time.Sleep(5 * time.Second) // Get C-3PO job resp, err := get(droidJobs[1]) @@ -349,8 +342,6 @@ if err != nil { log.Fatalln("Error scheduling job: ", err) } -time.Sleep(5 * time.Second) - // Get BB-8 job resp, err = get(droidJobs[2]) if err != nil { @@ -358,8 +349,6 @@ if err != nil { } fmt.Println("Get job response: ", resp) -time.Sleep(5 * time.Second) - // Delete BB-8 job err = delete(droidJobs[2]) if err != nil { @@ -473,7 +462,7 @@ cd jobs/go/sdk In the terminal, run the `job-service` app: ```bash -dapr run --app-id job-service --app-port 6200 --dapr-http-port 6280 --dapr-grpc-port 6281 --app-protocol grpc -- go run . +dapr run --app-id job-service --app-port 6200 --dapr-grpc-port 6281 --app-protocol grpc -- go run . ``` **Expected output** @@ -515,6 +504,301 @@ Return to the `job-service` app terminal window. The output should be: == APP == Executing maintenance job: Memory Wipe ``` +### What happened? + +When you ran the `dapr run` in this Quickstart for both the `job-scheduler` and the `job-service`, you can see the following jobs being registered, scheduled, retrieved, and deleted. + +- The `R2-D2` job is registered. +- The `C-3PO` job is registered. +- The `BB-8` job is registered. +- The `R2-D2` job is being scheduled. +- The `C-3PO` job is being scheduled. +- The `C-3PO` job is being retrieved. +- The `BB-8` job is being scheduled. +- The `BB-8` job is being retrieved. +- The `BB-8` job is being deleted. +- The `R2-D2` job is being executed after 5 seconds. +- The `R2-D2` job is being executed after 10 seconds. + +#### `job-service` app + +The `job-service` application creates service invocation handlers to manage the lifecycle of the job (`scheduleJob`, `getJob`, and `deleteJob`). + +```go +if err := server.AddServiceInvocationHandler("scheduleJob", scheduleJob); err != nil { + log.Fatalf("error adding invocation handler: %v", err) +} + +if err := server.AddServiceInvocationHandler("getJob", getJob); err != nil { + log.Fatalf("error adding invocation handler: %v", err) +} + +if err := server.AddServiceInvocationHandler("deleteJob", deleteJob); err != nil { + log.Fatalf("error adding invocation handler: %v", err) +} +``` + +Next, job event handlers are registered for all droids: + +```go +for _, jobName := range jobNames { + if err := server.AddJobEventHandler(jobName, handleJob); err != nil { + log.Fatalf("failed to register job event handler: %v", err) + } + fmt.Println("Registered job handler for: ", jobName) +} + +fmt.Println("Starting server on port: " + appPort) +if err = server.Start(); err != nil { + log.Fatalf("failed to start server: %v", err) +} +``` + +The `job-service` then codes the functions that handle scheduling, getting, deleting, and handling job events. + +```go +// Handler that schedules a DroidJob +func scheduleJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { + + if in == nil { + err = errors.New("no invocation parameter") + return + } + + droidJob := DroidJob{} + err = json.Unmarshal(in.Data, &droidJob) + if err != nil { + fmt.Println("failed to unmarshal job: ", err) + return nil, err + } + + jobData := JobData{ + Droid: droidJob.Name, + Task: droidJob.Job, + } + + content, err := json.Marshal(jobData) + if err != nil { + fmt.Printf("Error marshalling job content") + return nil, err + } + + // schedule job + job := daprc.Job{ + Name: droidJob.Name, + DueTime: droidJob.DueTime, + Data: &anypb.Any{ + Value: content, + }, + } + + err = app.daprClient.ScheduleJobAlpha1(ctx, &job) + if err != nil { + fmt.Println("failed to schedule job. err: ", err) + return nil, err + } + + fmt.Println("Job scheduled: ", droidJob.Name) + + out = &common.Content{ + Data: in.Data, + ContentType: in.ContentType, + DataTypeURL: in.DataTypeURL, + } + + return out, err + +} + +// Handler that gets a job by name +func getJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { + + if in == nil { + err = errors.New("no invocation parameter") + return nil, err + } + + job, err := app.daprClient.GetJobAlpha1(ctx, string(in.Data)) + if err != nil { + fmt.Println("failed to get job. err: ", err) + } + + out = &common.Content{ + Data: job.Data.Value, + ContentType: in.ContentType, + DataTypeURL: in.DataTypeURL, + } + + return out, err +} + +// Handler that deletes a job by name +func deleteJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { + if in == nil { + err = errors.New("no invocation parameter") + return nil, err + } + + err = app.daprClient.DeleteJobAlpha1(ctx, string(in.Data)) + if err != nil { + fmt.Println("failed to delete job. err: ", err) + } + + out = &common.Content{ + Data: in.Data, + ContentType: in.ContentType, + DataTypeURL: in.DataTypeURL, + } + + return out, err +} + +// Handler that handles job events +func handleJob(ctx context.Context, job *common.JobEvent) error { + var jobData common.Job + if err := json.Unmarshal(job.Data, &jobData); err != nil { + return fmt.Errorf("failed to unmarshal job: %v", err) + } + decodedPayload, err := base64.StdEncoding.DecodeString(jobData.Value) + if err != nil { + return fmt.Errorf("failed to decode job payload: %v", err) + } + var jobPayload JobData + if err := json.Unmarshal(decodedPayload, &jobPayload); err != nil { + return fmt.Errorf("failed to unmarshal payload: %v", err) + } + + fmt.Println("Starting droid:", jobPayload.Droid) + fmt.Println("Executing maintenance job:", jobPayload.Task) + + return nil +} +``` + +#### `job-scheduler` app + +In the `job-scheduler` application, the R2D2, C3PO, and BB8 jobs are first defined as `[]DroidJob`: + +```go +droidJobs := []DroidJob{ + {Name: "R2-D2", Job: "Oil Change", DueTime: "5s"}, + {Name: "C-3PO", Job: "Memory Wipe", DueTime: "15s"}, + {Name: "BB-8", Job: "Internal Gyroscope Check", DueTime: "30s"}, +} +``` + + +The jobs are then scheduled, retrieved, and deleted using the jobs API. As you can see from the terminal output, first the R2D2 job is scheduled: + +```go +// Schedule R2D2 job +err = schedule(droidJobs[0]) +if err != nil { + log.Fatalln("Error scheduling job: ", err) +} +``` + +Then, the C3PO job is scheduled, and returns job data: + +```go +// Schedule C-3PO job +err = schedule(droidJobs[1]) +if err != nil { + log.Fatalln("Error scheduling job: ", err) +} + +// Get C-3PO job +resp, err := get(droidJobs[1]) +if err != nil { + log.Fatalln("Error retrieving job: ", err) +} +fmt.Println("Get job response: ", resp) +``` + +The BB8 job is then scheduled, retrieved, and deleted: + +```go +// Schedule BB-8 job +err = schedule(droidJobs[2]) +if err != nil { + log.Fatalln("Error scheduling job: ", err) +} + +// Get BB-8 job +resp, err = get(droidJobs[2]) +if err != nil { + log.Fatalln("Error retrieving job: ", err) +} +fmt.Println("Get job response: ", resp) + +// Delete BB-8 job +err = delete(droidJobs[2]) +if err != nil { + log.Fatalln("Error deleting job: ", err) +} +fmt.Println("Job deleted: ", droidJobs[2].Name) +``` + +The `job-scheduler.go` also defines the `schedule`, `get`, and `delete` functions, calling from `job-service.go`. + +```go +// Schedules a job by invoking grpc service from job-service passing a DroidJob as an argument +func schedule(droidJob DroidJob) error { + jobData, err := json.Marshal(droidJob) + if err != nil { + fmt.Println("Error marshalling job content") + return err + } + + content := &daprc.DataContent{ + ContentType: "application/json", + Data: []byte(jobData), + } + + // Schedule Job + _, err = app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "scheduleJob", "POST", content) + if err != nil { + fmt.Println("Error invoking method: ", err) + return err + } + + return nil +} + +// Gets a job by invoking grpc service from job-service passing a job name as an argument +func get(droidJob DroidJob) (string, error) { + content := &daprc.DataContent{ + ContentType: "text/plain", + Data: []byte(droidJob.Name), + } + + //get job + resp, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "getJob", "GET", content) + if err != nil { + fmt.Println("Error invoking method: ", err) + return "", err + } + + return string(resp), nil +} + +// Deletes a job by invoking grpc service from job-service passing a job name as an argument +func delete(droidJob DroidJob) error { + content := &daprc.DataContent{ + ContentType: "text/plain", + Data: []byte(droidJob.Name), + } + + _, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "deleteJob", "DELETE", content) + if err != nil { + fmt.Println("Error invoking method: ", err) + return err + } + + return nil +} +``` + {{% /codetab %}} {{< /tabs >}} @@ -535,7 +819,7 @@ Join the discussion in our [discord channel](https://discord.com/channels/778680 ## Next steps - HTTP samples of this quickstart: - - [Go](todo) + - [Go](https://github.com/dapr/quickstarts/tree/master/jobs/go/http) - Learn more about [the jobs building block]({{< ref jobs-overview.md >}}) - Learn more about [the scheduler control plane]({{< ref scheduler.md >}}) From 434bbff274663af007ba78a5f63a488034ff7f08 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 29 Jul 2024 12:52:06 -0400 Subject: [PATCH 7/8] confirm code is up to date and link to what happened sections Signed-off-by: Hannah Hunter --- .../quickstarts/jobs-quickstart.md | 295 +----------------- 1 file changed, 1 insertion(+), 294 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md index 9be5deae8c1..a14f3c310c0 100644 --- a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md @@ -504,300 +504,7 @@ Return to the `job-service` app terminal window. The output should be: == APP == Executing maintenance job: Memory Wipe ``` -### What happened? - -When you ran the `dapr run` in this Quickstart for both the `job-scheduler` and the `job-service`, you can see the following jobs being registered, scheduled, retrieved, and deleted. - -- The `R2-D2` job is registered. -- The `C-3PO` job is registered. -- The `BB-8` job is registered. -- The `R2-D2` job is being scheduled. -- The `C-3PO` job is being scheduled. -- The `C-3PO` job is being retrieved. -- The `BB-8` job is being scheduled. -- The `BB-8` job is being retrieved. -- The `BB-8` job is being deleted. -- The `R2-D2` job is being executed after 5 seconds. -- The `R2-D2` job is being executed after 10 seconds. - -#### `job-service` app - -The `job-service` application creates service invocation handlers to manage the lifecycle of the job (`scheduleJob`, `getJob`, and `deleteJob`). - -```go -if err := server.AddServiceInvocationHandler("scheduleJob", scheduleJob); err != nil { - log.Fatalf("error adding invocation handler: %v", err) -} - -if err := server.AddServiceInvocationHandler("getJob", getJob); err != nil { - log.Fatalf("error adding invocation handler: %v", err) -} - -if err := server.AddServiceInvocationHandler("deleteJob", deleteJob); err != nil { - log.Fatalf("error adding invocation handler: %v", err) -} -``` - -Next, job event handlers are registered for all droids: - -```go -for _, jobName := range jobNames { - if err := server.AddJobEventHandler(jobName, handleJob); err != nil { - log.Fatalf("failed to register job event handler: %v", err) - } - fmt.Println("Registered job handler for: ", jobName) -} - -fmt.Println("Starting server on port: " + appPort) -if err = server.Start(); err != nil { - log.Fatalf("failed to start server: %v", err) -} -``` - -The `job-service` then codes the functions that handle scheduling, getting, deleting, and handling job events. - -```go -// Handler that schedules a DroidJob -func scheduleJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { - - if in == nil { - err = errors.New("no invocation parameter") - return - } - - droidJob := DroidJob{} - err = json.Unmarshal(in.Data, &droidJob) - if err != nil { - fmt.Println("failed to unmarshal job: ", err) - return nil, err - } - - jobData := JobData{ - Droid: droidJob.Name, - Task: droidJob.Job, - } - - content, err := json.Marshal(jobData) - if err != nil { - fmt.Printf("Error marshalling job content") - return nil, err - } - - // schedule job - job := daprc.Job{ - Name: droidJob.Name, - DueTime: droidJob.DueTime, - Data: &anypb.Any{ - Value: content, - }, - } - - err = app.daprClient.ScheduleJobAlpha1(ctx, &job) - if err != nil { - fmt.Println("failed to schedule job. err: ", err) - return nil, err - } - - fmt.Println("Job scheduled: ", droidJob.Name) - - out = &common.Content{ - Data: in.Data, - ContentType: in.ContentType, - DataTypeURL: in.DataTypeURL, - } - - return out, err - -} - -// Handler that gets a job by name -func getJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { - - if in == nil { - err = errors.New("no invocation parameter") - return nil, err - } - - job, err := app.daprClient.GetJobAlpha1(ctx, string(in.Data)) - if err != nil { - fmt.Println("failed to get job. err: ", err) - } - - out = &common.Content{ - Data: job.Data.Value, - ContentType: in.ContentType, - DataTypeURL: in.DataTypeURL, - } - - return out, err -} - -// Handler that deletes a job by name -func deleteJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { - if in == nil { - err = errors.New("no invocation parameter") - return nil, err - } - - err = app.daprClient.DeleteJobAlpha1(ctx, string(in.Data)) - if err != nil { - fmt.Println("failed to delete job. err: ", err) - } - - out = &common.Content{ - Data: in.Data, - ContentType: in.ContentType, - DataTypeURL: in.DataTypeURL, - } - - return out, err -} - -// Handler that handles job events -func handleJob(ctx context.Context, job *common.JobEvent) error { - var jobData common.Job - if err := json.Unmarshal(job.Data, &jobData); err != nil { - return fmt.Errorf("failed to unmarshal job: %v", err) - } - decodedPayload, err := base64.StdEncoding.DecodeString(jobData.Value) - if err != nil { - return fmt.Errorf("failed to decode job payload: %v", err) - } - var jobPayload JobData - if err := json.Unmarshal(decodedPayload, &jobPayload); err != nil { - return fmt.Errorf("failed to unmarshal payload: %v", err) - } - - fmt.Println("Starting droid:", jobPayload.Droid) - fmt.Println("Executing maintenance job:", jobPayload.Task) - - return nil -} -``` - -#### `job-scheduler` app - -In the `job-scheduler` application, the R2D2, C3PO, and BB8 jobs are first defined as `[]DroidJob`: - -```go -droidJobs := []DroidJob{ - {Name: "R2-D2", Job: "Oil Change", DueTime: "5s"}, - {Name: "C-3PO", Job: "Memory Wipe", DueTime: "15s"}, - {Name: "BB-8", Job: "Internal Gyroscope Check", DueTime: "30s"}, -} -``` - - -The jobs are then scheduled, retrieved, and deleted using the jobs API. As you can see from the terminal output, first the R2D2 job is scheduled: - -```go -// Schedule R2D2 job -err = schedule(droidJobs[0]) -if err != nil { - log.Fatalln("Error scheduling job: ", err) -} -``` - -Then, the C3PO job is scheduled, and returns job data: - -```go -// Schedule C-3PO job -err = schedule(droidJobs[1]) -if err != nil { - log.Fatalln("Error scheduling job: ", err) -} - -// Get C-3PO job -resp, err := get(droidJobs[1]) -if err != nil { - log.Fatalln("Error retrieving job: ", err) -} -fmt.Println("Get job response: ", resp) -``` - -The BB8 job is then scheduled, retrieved, and deleted: - -```go -// Schedule BB-8 job -err = schedule(droidJobs[2]) -if err != nil { - log.Fatalln("Error scheduling job: ", err) -} - -// Get BB-8 job -resp, err = get(droidJobs[2]) -if err != nil { - log.Fatalln("Error retrieving job: ", err) -} -fmt.Println("Get job response: ", resp) - -// Delete BB-8 job -err = delete(droidJobs[2]) -if err != nil { - log.Fatalln("Error deleting job: ", err) -} -fmt.Println("Job deleted: ", droidJobs[2].Name) -``` - -The `job-scheduler.go` also defines the `schedule`, `get`, and `delete` functions, calling from `job-service.go`. - -```go -// Schedules a job by invoking grpc service from job-service passing a DroidJob as an argument -func schedule(droidJob DroidJob) error { - jobData, err := json.Marshal(droidJob) - if err != nil { - fmt.Println("Error marshalling job content") - return err - } - - content := &daprc.DataContent{ - ContentType: "application/json", - Data: []byte(jobData), - } - - // Schedule Job - _, err = app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "scheduleJob", "POST", content) - if err != nil { - fmt.Println("Error invoking method: ", err) - return err - } - - return nil -} - -// Gets a job by invoking grpc service from job-service passing a job name as an argument -func get(droidJob DroidJob) (string, error) { - content := &daprc.DataContent{ - ContentType: "text/plain", - Data: []byte(droidJob.Name), - } - - //get job - resp, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "getJob", "GET", content) - if err != nil { - fmt.Println("Error invoking method: ", err) - return "", err - } - - return string(resp), nil -} - -// Deletes a job by invoking grpc service from job-service passing a job name as an argument -func delete(droidJob DroidJob) error { - content := &daprc.DataContent{ - ContentType: "text/plain", - Data: []byte(droidJob.Name), - } - - _, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "deleteJob", "DELETE", content) - if err != nil { - fmt.Println("Error invoking method: ", err) - return err - } - - return nil -} -``` +Unpack what happened in the [`job-service`]({{< ref "#job-service-app" >}}) and [`job-scheduler`]({{< ref "#job-scheduler-app" >}}) applications when you ran `dapr run`. {{% /codetab %}} From 7453e286a6d2a16832ef387984c0275ce24cd229 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:14:13 -0400 Subject: [PATCH 8/8] Update daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md Co-authored-by: Mark Fussell Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../content/en/getting-started/quickstarts/jobs-quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md index a14f3c310c0..96310518687 100644 --- a/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/jobs-quickstart.md @@ -172,7 +172,7 @@ if err = server.Start(); err != nil { } ``` -The `job-service` then codes the functions that handle scheduling, getting, deleting, and handling job events. +The `job-service` then call functions that handle scheduling, getting, deleting, and handling job events. ```go // Handler that schedules a DroidJob