Skip to content

Commit

Permalink
Merge pull request #989 from kaibocai/kaibocai/add-js-quickstart
Browse files Browse the repository at this point in the history
Add workflow quickstart for JS
  • Loading branch information
paulyuk committed Feb 27, 2024
2 parents 6a1984a + 1b8a451 commit 7429289
Show file tree
Hide file tree
Showing 19 changed files with 3,186 additions and 43 deletions.
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

0 comments on commit 7429289

Please sign in to comment.