Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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://raw.githubusercontent.com/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": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: the other properties uses _ and this one is using a capital letter, but since this payloads comes from runtime I guess there's no alternative than creating a issue to change this later (v1.11).

this will be seen as a breaking change but since wf is in alpha we are fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can change "WFInfo" in v1.11 since it requires a change to the components-contrib code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracking here: dapr/dapr#5911

"instance_id": "1234"
},
"start_time": "2023-02-02T23:34:53Z",
"metadata": {
"dapr.workflow.custom_status": "",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we fulfill the custom_status field with something meaningful ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can consider this for a future update.

"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