Skip to content

Commit

Permalink
Improved workflows example program and added in statestore functional…
Browse files Browse the repository at this point in the history
…ity. (dapr#1020)

* Workflow Management - Initial Methods (dapr#1003)

Initial work for workflows DotNET SDK

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Beefed up the workflows example program and added in statestore functionality

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Addressing a bunch of review comments

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Updates to readme and demo for workflows

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Changed webapp to console app

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Update DurableTask SDK dependency to get ARM64 compatibility (dapr#1024)

* Update DurableTask SDK dependency to get ARM64 compatibility

Signed-off-by: Chris Gillum <cgillum@microsoft.com>

* Fix issue with gRPC address override behavior

Signed-off-by: Chris Gillum <cgillum@microsoft.com>
Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Remove Web APIs and web dependencies

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Renaming WorkflowWebApp to WorkflowConsoleApp

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Various updates to the sample app

- Replaced DaprClient with WorkflowEngineClient
- Removed unused etag logic
- Fixed incorrect usage of certain model types
- Cleaned up logs and console output
- Simplified program loop
- Cleaned up console output and added some coloring
- Added error handling in the console interactions
- Various other tweaks/simplifications/enhancements

Signed-off-by: Chris Gillum <cgillum@microsoft.com>
Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Updates to README and demo http commands

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Make README copy/paste-able and some other minor tweaks

Signed-off-by: Chris Gillum <cgillum@microsoft.com>
Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Adding in Paul's devcontainer work

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* More README touch-ups

Signed-off-by: Chris Gillum <cgillum@microsoft.com>
Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* [docs] Add workflows to .NET client doc (dapr#1019)

* add workflows to client page

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>
Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Updating workflows readme and example

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* Fixing README for letting users know which .NET is needed

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

* moving using statements above the namespace

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>

---------

Signed-off-by: Ryan Lettieri <ryanLettieri@microsoft.com>
Signed-off-by: Chris Gillum <cgillum@microsoft.com>
Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>
Co-authored-by: Ryan Lettieri <ryanLettieri@microsoft.com>
Co-authored-by: Chris Gillum <cgillum@microsoft.com>
Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com>
  • Loading branch information
4 people authored and yash-nisar committed Feb 27, 2023
1 parent 1cde789 commit dfd4aeb
Show file tree
Hide file tree
Showing 20 changed files with 594 additions and 245 deletions.
29 changes: 29 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#
# Copyright 2021 The Dapr Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

ARG VARIANT=bullseye
FROM mcr.microsoft.com/vscode/devcontainers/dotnet:dev-7.0-bullseye

# Install minikube
RUN MINIKUBE_URL="https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64" \
&& sudo curl -sSL -o /usr/local/bin/minikube "${MINIKUBE_URL}" \
&& sudo chmod 0755 /usr/local/bin/minikube \
&& MINIKUBE_SHA256=$(curl -sSL "${MINIKUBE_URL}.sha256") \
&& echo "${MINIKUBE_SHA256} */usr/local/bin/minikube" | sha256sum -c -


# Install Dapr CLI
RUN wget -q https://github.com/raw/dapr/cli/master/install/install.sh -O - | /bin/bash

# Install Azure Dev CLI
RUN curl -fsSL https://aka.ms/install-azd.sh | bash
51 changes: 51 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "Azure Developer CLI",
"build": {
"dockerfile": "Dockerfile",
"args": {
"VARIANT": "bullseye"
}
},
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {
"version": "2.38"
},
"ghcr.io/devcontainers/features/docker-from-docker:1": {
"version": "20.10"
},
"ghcr.io/devcontainers/features/dotnet:1": {
"version": "6.0"
},
"ghcr.io/devcontainers/features/github-cli:1": {
"version": "2"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "16",
"nodeGypDependencies": false
}
},
"extensions": [
"ms-azuretools.azure-dev",
"ms-azuretools.vscode-bicep",
"ms-azuretools.vscode-docker",
"ms-vscode.vscode-node-azure-pack",
"ms-dotnettools.csharp",
"ms-dotnettools.vscode-dotnet-runtime",
"ms-azuretools.vscode-dapr",
"GitHub.copilot"
],
"forwardPorts": [
3000,
3100,
3500,
3501,
5000,
5007
],
"postCreateCommand": ".devcontainer/localinit.sh",
"remoteUser": "vscode",
"hostRequirements": {
"memory": "8gb"
}
}

9 changes: 9 additions & 0 deletions .devcontainer/localinit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# install Azure CLI extension for Container Apps
az config set extension.use_dynamic_install=yes_without_prompt
az extension add --name containerapp --yes

# install Node.js and NPM LTS
nvm install v18.12.1

# initialize Dapr
dapr init --runtime-version=1.10.0-rc.2
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ This repo builds the following packages:
- Dapr.Actors
- Dapr.Actors.AspNetCore
- Dapr.Extensions.Configuration
- Dapr.Workflow

### Prerequisites

Each project is a normal C# project. At minimum, you need [.NET 5.0 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) to build, test, and generate NuGet packages.
Each project is a normal C# project. At minimum, you need [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) to build, test, and generate NuGet packages.

Also make sure to reference the [.NET SDK contribution guide](https://docs.dapr.io/contributing/sdk-contrib/dotnet-contributing/)

Expand Down
9 changes: 6 additions & 3 deletions all.sln
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,17 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Workflow", "src\Dapr.Workflow\Dapr.Workflow.csproj", "{07578B6C-9B96-4B3D-BA2E-7800EFCA7F99}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflow", "Workflow", "{BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}"
ProjectSection(SolutionItems) = preProject
examples\Workflow\README.md = examples\Workflow\README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowWebApp", "examples\Workflow\WorkflowWebApp\WorkflowWebApp.csproj", "{5C61ABED-7623-4C28-A5C9-C5972A0F669C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowConsoleApp", "examples\Workflow\WorkflowConsoleApp\WorkflowConsoleApp.csproj", "{5C61ABED-7623-4C28-A5C9-C5972A0F669C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PublishSubscribe", "PublishSubscribe", "{0EF6EA64-D7C3-420D-9890-EAE8D54A57E6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublishEventExample", "examples\Client\PublishSubscribe\PublishEventExample\PublishEventExample.csproj", "{4A175C27-EAFE-47E7-90F6-873B37863656}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublishEventExample", "examples\Client\PublishSubscribe\PublishEventExample\PublishEventExample.csproj", "{4A175C27-EAFE-47E7-90F6-873B37863656}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BulkPublishEventExample", "examples\Client\PublishSubscribe\BulkPublishEventExample\BulkPublishEventExample.csproj", "{DDC41278-FB60-403A-B969-2AEBD7C2D83C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BulkPublishEventExample", "examples\Client\PublishSubscribe\BulkPublishEventExample\BulkPublishEventExample.csproj", "{DDC41278-FB60-403A-B969-2AEBD7C2D83C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
132 changes: 70 additions & 62 deletions examples/Workflow/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dapr Workflow with ASP.NET Core sample

This Dapr workflow example shows how to create a Dapr workflow (`Workflow`) and invoke it using ASP.NET Core web APIs.
This Dapr workflow example shows how to create a Dapr workflow (`Workflow`) and invoke it using the console.

## Prerequisites

Expand All @@ -11,105 +11,113 @@ This Dapr workflow example shows how to create a Dapr workflow (`Workflow`) and

## Projects in sample

This sample contains a single [WorkflowWebApp](./WorkflowWebApp) ASP.NET Core project. It combines both the workflow implementations and the web APIs for starting and querying workflows instances.
This sample contains a single [WorkflowConsoleApp](./WorkflowConsoleApp) .NET project. It utilizes the workflow SDK as well as the workflow management API for starting and querying workflows instances.

The main `Program.cs` file contains the main setup of the app, including the registration of the web APIs and the registration of the workflow and workflow activities. The workflow definition is found in `Workflows` directory and the workflow activity definitions are found in the `Activities` directory.
The main `Program.cs` file contains the main setup of the app, including the registration of the workflow and workflow activities. The workflow definition is found in the `Workflows` directory and the workflow activity definitions are found in the `Activities` directory.

## Running the example

To run the workflow web app locally, run this command in the `WorkflowWebApp` directory:
To run the workflow web app locally, two separate terminal windows are required.
In the first terminal window, from the `WorkflowConsoleApp` directory, run the following command to start the program itself:

```sh
dapr run --app-id wfwebapp dotnet run
dotnet run
```

The application will listen for HTTP requests at `http://localhost:10080`.
Next, in a separate terminal window, start the dapr sidecar:

To start a workflow, use the following command to send an HTTP POST request, which triggers an HTTP API that starts the workflow using the Dapr Workflow client. Two identical `curl` commands are shown, one for Linux/macOS (bash) and the other for Windows (PowerShell). The body of the request is used as the input of the workflow.
```sh
dapr run --app-id wfapp --dapr-grpc-port 4001 --dapr-http-port 3500
```

Dapr listens for HTTP requests at `http://localhost:3500`.

This example illustrates a purchase order processing workflow. The console prompts provide directions on how to both purchase and restock items.

To start a workflow, you have two options:

On Linux/macOS (bash):
1. Follow the directions from the console prompts.
2. Use the workflows API and send a request to Dapr directly. Examples are included below as well as in the "demo.http" file down the "WorkflowConsoleApp" directory.

For the workflow API option, two identical `curl` commands are shown, one for Linux/macOS (bash) and the other for Windows (PowerShell). The body of the request is the purchase order information used as the input of the workflow.

Make note of the "1234" in the commands below. This represents the unique identifier for the workflow run and can be replaced with any identifier of your choosing.

```bash
curl -i -X POST http://localhost:10080/orders \
curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/1234/start \
-H "Content-Type: application/json" \
-d '{"name": "Paperclips", "totalCost": 99.95, "quantity": 1}'
-d '{ "input" : {"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}}'
```

On Windows (PowerShell):

```powershell
curl -i -X POST http://localhost:10080/orders `
curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/1234/start `
-H "Content-Type: application/json" `
-d '{"name": "Paperclips", "totalCost": 99.95, "quantity": 1}'
-d '{ "input" : {"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}}'
```

If successful, you should see a response like the following, which contains a `Location` header pointing to a status endpoint for the workflow that was created with a randomly generated 8-digit ID:
If successful, you should see a response like the following:

```http
HTTP/1.1 202 Accepted
Content-Length: 0
Date: Tue, 24 Jan 2023 00:02:02 GMT
Server: Kestrel
Location: http://localhost:10080/orders/cdcce425
```json
{"instance_id":"1234"}
```

Next, send an HTTP request to the URL in the `Location` header in the previous HTTP response, like in the following example:
Next, send an HTTP request to get the status of the workflow that was started:

```bash
curl -i http://localhost:10080/orders/cdcce425
curl -i -X GET http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/1234
```

If the workflow has completed running, you should see the following output (formatted for readability):

```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 24 Jan 2023 00:10:53 GMT
Server: Kestrel
Transfer-Encoding: chunked
The workflow is designed to take several seconds to complete. If the workflow hasn't completed yet when you issue the previous command, you should see the following JSON response (formatted for readability):

```json
{
"details": {
"name": "Paperclips",
"quantity": 1,
"totalCost": 99.95
},
"result": {
"processed": true
},
"status": "Completed"
"WFInfo": {
"instance_id": "1234"
},
"start_time": "2023-02-02T23:34:53Z",
"metadata": {
"dapr.workflow.custom_status": "",
"dapr.workflow.input": "{\"Name\":\"Paperclips\",\"Quantity\":1,\"TotalCost\":99.95}",
"dapr.workflow.last_updated": "2023-02-02T23:35:07Z",
"dapr.workflow.name": "OrderProcessingWorkflow",
"dapr.workflow.output": "{\"Processed\":true}",
"dapr.workflow.runtime_status": "RUNNING"
}
}
```

If the workflow hasn't completed yet, you might instead see the following:

```http
HTTP/1.1 202 Accepted
Content-Type: application/json; charset=utf-8
Date: Tue, 24 Jan 2023 00:17:49 GMT
Location: http://localhost:10080/orders/cdcce425
Server: Kestrel
Transfer-Encoding: chunked
Once the workflow has completed running, you should see the following output, indicating that it has reached the "COMPLETED" status:

```json
{
"details": {
"name": "Paperclips",
"quantity": 1,
"totalCost": 99.95
},
"status": "Running"
"WFInfo": {
"instance_id": "1234"
},
"start_time": "2023-02-02T23:34:53Z",
"metadata": {
"dapr.workflow.custom_status": "",
"dapr.workflow.input": "{\"Name\":\"Paperclips\",\"Quantity\":1,\"TotalCost\":99.95}",
"dapr.workflow.last_updated": "2023-02-02T23:35:07Z",
"dapr.workflow.name": "OrderProcessingWorkflow",
"dapr.workflow.output": "{\"Processed\":true}",
"dapr.workflow.runtime_status": "COMPLETED"
}
}
```

When the workflow has completed, the stdout of the web app should look like the following:
When the workflow has completed, the stdout of the workflow app should look like the following:

```log
info: WorkflowWebApp.Activities.NotifyActivity[0]
Received order cdcce425 for Paperclips at $99.95
info: WorkflowWebApp.Activities.ReserveInventoryActivity[0]
Reserving inventory: cdcce425, Paperclips, 1
info: WorkflowWebApp.Activities.ProcessPaymentActivity[0]
Processing payment: cdcce425, 99.95, USD
info: WorkflowWebApp.Activities.NotifyActivity[0]
Order cdcce425 processed successfully!
info: WorkflowConsoleApp.Activities.NotifyActivity[0]
Received order 1234 for Paperclips at $99.95
info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[0]
Reserving inventory: 1234, Paperclips, 1
info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[0]
Processing payment: 1234, 99.95, USD
info: WorkflowConsoleApp.Activities.NotifyActivity[0]
Order 1234 processed successfully!
```

If you have Zipkin configured for Dapr locally on your machine, then you can view the workflow trace spans in the Zipkin web UI (typically at http://localhost:9411/zipkin/).
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
namespace WorkflowWebApp.Activities
{
using System.Threading.Tasks;
using Dapr.Workflow;
using System.Threading.Tasks;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;

namespace WorkflowConsoleApp.Activities
{
record Notification(string Message);

class NotifyActivity : WorkflowActivity<Notification, object>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
namespace WorkflowWebApp.Activities
{
using System.Threading.Tasks;
using Dapr.Workflow;

record PaymentRequest(string RequestId, double Amount, string Currency);
using System.Threading.Tasks;
using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
using WorkflowConsoleApp.Models;

namespace WorkflowConsoleApp.Activities
{
class ProcessPaymentActivity : WorkflowActivity<PaymentRequest, object>
{
readonly ILogger logger;
readonly DaprClient client;

public ProcessPaymentActivity(ILoggerFactory loggerFactory)
public ProcessPaymentActivity(ILoggerFactory loggerFactory, DaprClient client)
{
this.logger = loggerFactory.CreateLogger<ProcessPaymentActivity>();
this.client = client;
}

public override async Task<object> RunAsync(WorkflowActivityContext context, PaymentRequest req)
{
this.logger.LogInformation(
"Processing payment: {requestId}, {amount}, {currency}",
"Processing payment: {requestId} for {amount} {item} at ${currency}",
req.RequestId,
req.Amount,
req.ItemName,
req.Currency);

// Simulate slow processing
Expand Down
Loading

0 comments on commit dfd4aeb

Please sign in to comment.