Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add workflow quickstart for JS #989

Merged
merged 21 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3d9799f
use InvokeHttpClient in service invocation csharp sample
MregXN Nov 21, 2023
cb2075f
add dependency
MregXN Nov 21, 2023
5097d9a
retrigger to pass validation
MregXN Nov 21, 2023
0453824
use daprWorkflowClient
MregXN Dec 14, 2023
48842ed
Clarified the actor state operations performed and what subsequent ac…
WhitWaldo Dec 30, 2023
cea9916
Updated comment to reflect call out on Discord by Pundil
WhitWaldo Dec 30, 2023
0f93b1d
Updated comment to reflect call out on Discord by Pundil
WhitWaldo Dec 30, 2023
b6a1a5e
Merge remote-tracking branch 'origin/updated-actor-csharp-quickstart-…
WhitWaldo Dec 30, 2023
334b35a
Merge pull request #979 from paulyuk/release-1.13
paulyuk Feb 10, 2024
873eecb
Revert "Fix tests broken by timing change"
paulyuk Feb 10, 2024
cfc5de1
Merge pull request #983 from dapr/revert-979-release-1.13
paulyuk Feb 10, 2024
583e9b3
Merge pull request #902 from MregXN/use-daprclient
paulyuk Feb 11, 2024
d718efe
Merge pull request #961 from WhitWaldo/updated-actor-csharp-quickstar…
paulyuk Feb 11, 2024
a933afe
Merge pull request #959 from MregXN/remove-daprClient-for-workflow
paulyuk Feb 11, 2024
e0ec38b
bump go to 1.21
mikeee Feb 13, 2024
474c283
bump short timeouts
mikeee Feb 13, 2024
ec1e515
Merge pull request #986 from mikeee/bump-go-validation
paulyuk Feb 14, 2024
2d7b401
add quickstart for JS
kaibocai Feb 16, 2024
5cfcd0e
fix readme check
kaibocai Feb 20, 2024
9aecc35
minor refactory
kaibocai Feb 27, 2024
1b8a451
Merge branch 'release-1.13' into kaibocai/add-js-quickstart
paulyuk Feb 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ release.properties
mvnw
packages
**/__pycache__/
**/dist/
Debug/

# IDE generated files and directories
Expand Down
11 changes: 7 additions & 4 deletions actors/csharp/sdk/service/SmokeDetectorActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected override Task OnActivateAsync()
/// </summary>
protected override Task OnDeactivateAsync()
{
// Provides Opportunity to perform optional cleanup.
// Provides opportunity to perform optional cleanup.
Console.WriteLine($"Deactivating actor id: {Id}");
return Task.CompletedTask;
}
Expand All @@ -47,9 +47,12 @@ protected override Task OnDeactivateAsync()
/// <param name="data">the user-defined MyData which will be stored into state store as "device_data" state</param>
public async Task<string> SetDataAsync(SmartDeviceData data)
{
// Data is saved to configured state store *implicitly* after each method execution by Actor's runtime.
// Data can also be saved *explicitly* by calling this.StateManager.SaveStateAsync();
// State to be saved must be DataContract serializable.
// This set state action can happen along other state changing operations in each actor method and those changes will be maintained
// in a local cache to be committed as a single transaction to the backing store when the method has completed. As such, there is
// no need to (and in fact makes your code less transactional) call `this.StateManager.SaveStateAsync()` as it will be automatically
// invoked by the actor runtime following the conclusion of this method as part of the internal `OnPostActorMethodAsyncInternal` method.

// Note also that all saved state must be DataContract serializable.
await StateManager.SetStateAsync<SmartDeviceData>(
deviceDataKey,
data);
Expand Down
16 changes: 7 additions & 9 deletions service_invocation/csharp/http/checkout/Program.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Dapr.Client;

var baseURL = (Environment.GetEnvironmentVariable("BASE_URL") ?? "http://localhost") + ":" + (Environment.GetEnvironmentVariable("DAPR_HTTP_PORT") ?? "3500");

var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
// Adding app id as part of the header
client.DefaultRequestHeaders.Add("dapr-app-id", "order-processor");
var client = DaprClient.CreateInvokeHttpClient(appId: "order-processor");

for (int i = 1; i <= 20; i++) {
var order = new Order(i);
var orderJson = JsonSerializer.Serialize<Order>(order);
var content = new StringContent(orderJson, Encoding.UTF8, "application/json");

var cts = new CancellationTokenSource();
Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => cts.Cancel();

// Invoking a service
var response = await client.PostAsync($"{baseURL}/orders", content);
var response = await client.PostAsJsonAsync("/orders", order, cts.Token);

Console.WriteLine("Order passed: " + order);

await Task.Delay(TimeSpan.FromSeconds(1));
Expand Down
7 changes: 6 additions & 1 deletion service_invocation/csharp/http/checkout/checkout.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

</Project>
<ItemGroup>
<PackageReference Include="Dapr.Client" Version="1.12.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion state_management/csharp/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ sleep: 15
cd ./order-processor
dapr run --app-id order-processor --resources-path ../../../resources/ -- dotnet run
```
<!-- END_STEP -->

2. Stop and clean up application processes

dapr stop --app-id order-processor
<!-- END_STEP -->
4 changes: 1 addition & 3 deletions state_management/csharp/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,8 @@ You're up and running! Both Dapr and your app logs will appear here.
== APP == Getting Order: Order { orderId = 3 }
== APP == Deleting Order: Order { orderId = 3 }
```
<!-- END_STEP -->

2. Stop and clean up application processes

```bash
dapr stop --app-id order-processor
```
<!-- END_STEP -->
5 changes: 2 additions & 3 deletions state_management/go/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ You're up and running! Both Dapr and your app logs will appear here.
== APP == Retrieved Order: "{\"orderId\":3}"
== APP == 2023/09/24 23:31:27 Deleted Order: {"orderId":3}
```
<!-- END_STEP -->

2. Stop and clean up application processes
```bash
dapr stop --app-id order-processor
```

<!-- END_STEP -->
5 changes: 2 additions & 3 deletions state_management/go/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,8 @@ You're up and running! Both Dapr and your app logs will appear here.
== APP - order-processor == Retrieved Order: {"orderId":2}
== APP - order-processor == Deleted Order: {"orderId":2}
```
<!-- END_STEP -->

2. Stop and clean up application processes
```bash
dapr stop --app-id order-processor
```

<!-- END_STEP -->
3 changes: 1 addition & 2 deletions state_management/javascript/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,10 @@ sleep: 60
```bash
dapr run --app-id order-processor --resources-path ../../../resources/ -- npm start
```
<!-- END_STEP -->

2. Stop and cleanup the process

```bash
dapr stop --app-id order-processor
```

<!-- END_STEP -->
29 changes: 12 additions & 17 deletions workflows/csharp/sdk/order-processor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@

using var daprClient = new DaprClientBuilder().Build();

// NOTE: WorkflowEngineClient will be replaced with a richer version of DaprClient
// in a subsequent SDK release. This is a temporary workaround.
WorkflowEngineClient workflowClient = host.Services.GetRequiredService<WorkflowEngineClient>();
DaprWorkflowClient workflowClient = host.Services.GetRequiredService<DaprWorkflowClient>();

// Generate a unique ID for the workflow
string orderId = Guid.NewGuid().ToString()[..8];
Expand All @@ -51,28 +49,25 @@
// Start the workflow
Console.WriteLine("Starting workflow {0} purchasing {1} {2}", orderId, ammountToPurchase, itemToPurchase);

await daprClient.StartWorkflowAsync(
workflowComponent: DaprWorkflowComponent,
workflowName: nameof(OrderProcessingWorkflow),
input: orderInfo,
instanceId: orderId);
await workflowClient.ScheduleNewWorkflowAsync(
name: nameof(OrderProcessingWorkflow),
instanceId: orderId,
input: orderInfo);

// Wait for the workflow to start and confirm the input
GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync(
instanceId: orderId,
workflowComponent: DaprWorkflowComponent);
WorkflowState state = await workflowClient.WaitForWorkflowStartAsync(
instanceId: orderId);

Console.WriteLine("Your workflow has started. Here is the status of the workflow: {0}", state.RuntimeStatus);
Console.WriteLine("Your workflow has started. Here is the status of the workflow: {0}", Enum.GetName(typeof(WorkflowRuntimeStatus), state.RuntimeStatus));

// Wait for the workflow to complete
state = await daprClient.WaitForWorkflowCompletionAsync(
instanceId: orderId,
workflowComponent: DaprWorkflowComponent);
state = await workflowClient.WaitForWorkflowCompletionAsync(
instanceId: orderId);

Console.WriteLine("Workflow Status: {0}", state.RuntimeStatus);
Console.WriteLine("Workflow Status: {0}", Enum.GetName(typeof(WorkflowRuntimeStatus), state.RuntimeStatus));

void RestockInventory(string itemToPurchase)
{
daprClient.SaveStateAsync<OrderPayload>(StoreName, itemToPurchase, new OrderPayload(Name: itemToPurchase, TotalCost: 15000, Quantity: 100));
daprClient.SaveStateAsync<OrderPayload>(StoreName, itemToPurchase, new OrderPayload(Name: itemToPurchase, TotalCost: 15000, Quantity: 100));
return;
}
146 changes: 146 additions & 0 deletions workflows/javascript/sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Dapr workflows

In this quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management API. The console app starts and manages the lifecycle of a workflow that stores and retrieves data in a state store.

This quickstart includes one project:

- JavaScript console app `order-processor`

The quickstart contains 1 workflow to simulate purchasing items from a store, and 5 unique activities within the workflow. These 5 activities are as follows:

- notifyActivity: This activity utilizes a logger to print out messages throughout the workflow. These messages notify the user when there is insufficient inventory, their payment couldn't be processed, and more.
- reserveInventoryActivity: This activity checks the state store to ensure that there is enough inventory present for purchase.
- requestApprovalActivity: This activity requests approval for orders over a certain threshold
- processPaymentActivity: This activity is responsible for processing and authorizing the payment.
- updateInventoryActivity: This activity updates the state store with the new remaining inventory value.

### Run the order processor workflow with multi-app-run

1. Open a new terminal window and navigate to `order-processor` directory:

<!-- STEP
name: build order-process app
-->

```bash
cd ./javascript/sdk
npm install
npm run build
```

<!-- END_STEP -->
2. Run the console app with Dapr:

<!-- STEP
name: Run order-processor service
expected_stdout_lines:
- '== APP - workflowApp == == APP == Payment of 100 for 10 item1 processed successfully'
- 'there are now 90 item1 in stock'
- 'processed successfully!'
expected_stderr_lines:
output_match_mode: substring
background: true
sleep: 15
timeout_seconds: 120
-->

```bash
dapr run -f .
```

<!-- END_STEP -->

3. Expected output


```
== APP - workflowApp == == APP == Orchestration scheduled with ID: 0c332155-1e02-453a-a333-28cfc7777642
== APP - workflowApp == == APP == Waiting 30 seconds for instance 0c332155-1e02-453a-a333-28cfc7777642 to complete...
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 0 history event...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, EXECUTIONSTARTED=1]
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == Received order 0c332155-1e02-453a-a333-28cfc7777642 for 10 item1 at a total cost of 100
== APP - workflowApp == == APP == Activity notifyActivity completed with output undefined (0 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 3 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == Reserving inventory for 0c332155-1e02-453a-a333-28cfc7777642 of 10 item1
== APP - workflowApp == == APP == 2024-02-16T03:15:59.498Z INFO [HTTPClient, HTTPClient] Sidecar Started
== APP - workflowApp == == APP == There are 100 item1 in stock
== APP - workflowApp == == APP == Activity reserveInventoryActivity completed with output {"success":true,"inventoryItem":{"perItemCost":100,"quantity":100,"itemName":"item1"}} (86 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 6 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == Processing payment for order item1
== APP - workflowApp == == APP == Payment of 100 for 10 item1 processed successfully
== APP - workflowApp == == APP == Activity processPaymentActivity completed with output true (4 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 9 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == Updating inventory for 0c332155-1e02-453a-a333-28cfc7777642 of 10 item1
== APP - workflowApp == == APP == Inventory updated for 0c332155-1e02-453a-a333-28cfc7777642, there are now 90 item1 in stock
== APP - workflowApp == == APP == Activity updateInventoryActivity completed with output {"success":true,"inventoryItem":{"perItemCost":100,"quantity":90,"itemName":"item1"}} (85 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 12 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == order 0c332155-1e02-453a-a333-28cfc7777642 processed successfully!
== APP - workflowApp == == APP == Activity notifyActivity completed with output undefined (0 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 15 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == Order 0c332155-1e02-453a-a333-28cfc7777642 processed successfully!
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Orchestration completed with status COMPLETED
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == time="2024-02-15T21:15:59.5589687-06:00" level=info msg="0c332155-1e02-453a-a333-28cfc7777642: 'orderProcessingWorkflow' completed with a COMPLETED status." app_id=activity-sequence-workflow instance=kaibocai-devbox scope=wfengine.backend type=log ver=1.12.4
== APP - workflowApp == == APP == Instance 0c332155-1e02-453a-a333-28cfc7777642 completed
```

### View workflow output with Zipkin

For a more detailed view of the workflow activities (duration, progress etc.), try using Zipkin.

1. Launch Zipkin container - The [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) docker container is launched on running `dapr init`. Check to make sure the container is running. If it's not, launch the Zipkin docker container with the following command.

```bash
docker run -d -p 9411:9411 openzipkin/zipkin
```

2. View Traces in Zipkin UI - In your browser go to http://localhost:9411 to view the workflow trace spans in the Zipkin web UI. The order-processor workflow should be viewable with the following output in the Zipkin web UI.

<img src="img/workflow-trace-spans-zipkin.png">

### What happened?

When you ran `dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 50001 --components-path ../../components npm run start:order-process`

1. A unique order ID for the workflow is generated (in the above example, `0c332155-1e02-453a-a333-28cfc7777642`) and the workflow is scheduled.
2. The `notifyActivity` workflow activity sends a notification saying an order for 10 cars has been received.
3. The `reserveInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock.
4. Your workflow starts and notifies you of its status.
5. The `requestApprovalActivity` workflow activity requests approval for order `0c332155-1e02-453a-a333-28cfc7777642`
6. The `processPaymentActivity` workflow activity begins processing payment for order `0c332155-1e02-453a-a333-28cfc7777642` and confirms if successful.
7. The `updateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed.
8. The `notifyActivity` workflow activity sends a notification saying that order `0c332155-1e02-453a-a333-28cfc7777642` has completed and processed.
9. The workflow terminates as completed and processed.

7 changes: 7 additions & 0 deletions workflows/javascript/sdk/dapr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 1
common:
resourcesPath: ../../components
apps:
- appID: workflowApp
appDirPath: ./order-processor/
command: ["npm", "run", "start:dapr:order-process"]
2 changes: 2 additions & 0 deletions workflows/javascript/sdk/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include ../../../docker.mk
include ../../../validate.mk