Merge branch 'release-1.13' of github.com:dapr/quickstarts into release-1.13

This commit is contained in:
Paul Yuknewicz 2024-02-27 11:36:23 -08:00
commit a35571fc9a
21 changed files with 3188 additions and 50 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ release.properties
mvnw
packages
**/__pycache__/
**/dist/
Debug/
# IDE generated files and directories

View File

@ -36,7 +36,7 @@ internal class SmokeDetectorActor : Actor, ISmartDevice
/// </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;
}
@ -47,9 +47,12 @@ internal class SmokeDetectorActor : Actor, ISmartDevice
/// <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);

View File

@ -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));

View File

@ -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>

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -22,7 +22,7 @@ app.use(bodyParser.json());
const daprPort = process.env.DAPR_HTTP_PORT ?? "3500";
const daprGRPCPort = process.env.DAPR_GRPC_PORT ?? "50001";
const stateStoreName = `statestore`;
const stateStoreName = process.env.STATE_STORE_NAME ?? "statestore";
const stateUrl = `http://localhost:${daprPort}/v1.0/state/${stateStoreName}`;
const port = process.env.APP_PORT ?? "3000";
@ -76,4 +76,4 @@ app.get('/ports', (_req, res) => {
res.status(200).send({DAPR_HTTP_PORT: daprPort, DAPR_GRPC_PORT: daprGRPCPort })
});
app.listen(port, () => console.log(`Node App listening on port ${port}!`));
app.listen(port, () => console.log(`Node App listening on port ${port}!`));

View File

@ -33,9 +33,7 @@ host.Start();
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];
@ -51,28 +49,25 @@ OrderPayload orderInfo = new OrderPayload(itemToPurchase, 15000, ammountToPurcha
// 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;
}

View File

@ -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.

View File

@ -0,0 +1,7 @@
version: 1
common:
resourcesPath: ../../components
apps:
- appID: workflowApp
appDirPath: ./order-processor/
command: ["npm", "run", "start:dapr:order-process"]

View File

@ -0,0 +1,2 @@
include ../../../docker.mk
include ../../../validate.mk

View File

@ -0,0 +1,107 @@
export class OrderPayload {
itemName: string;
totalCost: number;
quantity: number;
constructor(itemName: string, totalCost: number, quantity: number) {
this.itemName = itemName;
this.totalCost = totalCost;
this.quantity = quantity;
}
toJson(): string {
return `{"itemName": "${this.itemName}", "quantity": ${this.quantity}, "totalCost": ${this.totalCost}}`;
}
toString(): string {
return `OrderPayload(name=${this.itemName}, totalCost=${this.totalCost}, quantity=${this.quantity})`;
}
}
export class InventoryItem {
itemName: string;
perItemCost: number;
quantity: number;
constructor(itemName: string, perItemCost: number, quantity: number) {
this.itemName = itemName;
this.perItemCost = perItemCost;
this.quantity = quantity;
}
toString(): string {
return `InventoryItem(itemName=${this.itemName}, perItemCost=${this.perItemCost}, quantity=${this.quantity})`;
}
}
export class InventoryRequest {
requestId: string;
itemName: string;
quantity: number;
constructor(requestId: string, itemName: string, quantity: number) {
this.requestId = requestId;
this.itemName = itemName;
this.quantity = quantity;
}
toString(): string {
return `InventoryRequest(requestId=${this.requestId}, itemName=${this.itemName}, quantity=${this.quantity})`;
}
}
export class InventoryResult {
success: boolean;
inventoryItem?: InventoryItem;
constructor(success: boolean, inventoryItem: InventoryItem | undefined) {
this.success = success;
this.inventoryItem = inventoryItem;
}
toString(): string {
return `InventoryResult(success=${this.success}, inventoryItem=${this.inventoryItem})`;
}
}
export class OrderPaymentRequest {
requestId: string;
itemBeingPurchased: string;
amount: number;
quantity: number;
constructor(requestId: string, itemBeingPurchased: string, amount: number, quantity: number) {
this.requestId = requestId;
this.itemBeingPurchased = itemBeingPurchased;
this.amount = amount;
this.quantity = quantity;
}
toString(): string {
return `PaymentRequest(requestId=${this.requestId}, itemBeingPurchased=${this.itemBeingPurchased}, amount=${this.amount}, quantity=${this.quantity})`;
}
}
export class ApprovalRequired {
approval: boolean;
constructor(approval: boolean) {
this.approval = approval;
}
toString(): string {
return `ApprovalRequired(approval=${this.approval})`;
}
}
export class OrderNotification {
message: string;
constructor(message: string) {
this.message = message;
}
toString(): string {
return `Notification(message=${this.message})`;
}
}

View File

@ -0,0 +1,119 @@
import { WorkflowActivityContext, WorkflowContext, TWorkflow, DaprClient } from "@dapr/dapr-dev";
import { InventoryItem, InventoryRequest, InventoryResult, OrderNotification, OrderPayload, OrderPaymentRequest } from "./model";
const daprClient = new DaprClient();
const storeName = "statestore";
// Defines Notify Activity. This is used by the workflow to send out a notification
export const notifyActivity = async (_: WorkflowActivityContext, orderNotification: OrderNotification) => {
console.log(orderNotification.message);
return;
};
//Defines Reserve Inventory Activity. This is used by the workflow to verify if inventory is available for the order
export const reserveInventoryActivity = async (_: WorkflowActivityContext, inventoryRequest: InventoryRequest) => {
console.log(`Reserving inventory for ${inventoryRequest.requestId} of ${inventoryRequest.quantity} ${inventoryRequest.itemName}`);
const result = await daprClient.state.get(storeName, inventoryRequest.itemName);
if (result == undefined || result == null) {
return new InventoryResult(false, undefined);
}
const inventoryItem = result as InventoryItem;
console.log(`There are ${inventoryItem.quantity} ${inventoryItem.itemName} in stock`);
if (inventoryItem.quantity >= inventoryRequest.quantity) {
return new InventoryResult(true, inventoryItem)
}
return new InventoryResult(false, undefined);
}
export const requestApprovalActivity = async (_: WorkflowActivityContext, orderPayLoad: OrderPayload) => {
console.log(`Requesting approval for order ${orderPayLoad.itemName}`);
return true;
}
export const processPaymentActivity = async (_: WorkflowActivityContext, orderPaymentRequest: OrderPaymentRequest) => {
console.log(`Processing payment for order ${orderPaymentRequest.itemBeingPurchased}`);
console.log(`Payment of ${orderPaymentRequest.amount} for ${orderPaymentRequest.quantity} ${orderPaymentRequest.itemBeingPurchased} processed successfully`);
return true;
}
export const updateInventoryActivity = async (_: WorkflowActivityContext, inventoryRequest: InventoryRequest) => {
console.log(`Updating inventory for ${inventoryRequest.requestId} of ${inventoryRequest.quantity} ${inventoryRequest.itemName}`);
const result = await daprClient.state.get(storeName, inventoryRequest.itemName);
if (result == undefined || result == null) {
return new InventoryResult(false, undefined);
}
const inventoryItem = result as InventoryItem;
inventoryItem.quantity = inventoryItem.quantity - inventoryRequest.quantity;
if (inventoryItem.quantity < 0) {
console.log(`Insufficient inventory for ${inventoryRequest.requestId} of ${inventoryRequest.quantity} ${inventoryRequest.itemName}`);
return new InventoryResult(false, undefined);
}
await daprClient.state.save(storeName, [
{
key: inventoryRequest.itemName,
value: inventoryItem,
}
]);
console.log(`Inventory updated for ${inventoryRequest.requestId}, there are now ${inventoryItem.quantity} ${inventoryItem.itemName} in stock`);
return new InventoryResult(true, inventoryItem);
}
export const orderProcessingWorkflow: TWorkflow = async function* (ctx: WorkflowContext, orderPayLoad: OrderPayload): any {
const orderId = ctx.getWorkflowInstanceId();
console.log(`Processing order ${orderId}...`);
const orderNotification: OrderNotification = {
message: `Received order ${orderId} for ${orderPayLoad.quantity} ${orderPayLoad.itemName} at a total cost of ${orderPayLoad.totalCost}`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
const inventoryRequest = new InventoryRequest(orderId, orderPayLoad.itemName, orderPayLoad.quantity);
const inventoryResult = yield ctx.callActivity(reserveInventoryActivity, inventoryRequest);
if (!inventoryResult.success) {
const orderNotification: OrderNotification = {
message: `Insufficient inventory for order ${orderId}`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return;
}
if (orderPayLoad.totalCost > 5000) {
const approvalResult = yield ctx.callActivity(requestApprovalActivity, orderPayLoad);
if (!approvalResult) {
const orderNotification: OrderNotification = {
message: `Order ${orderId} approval denied`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return;
}
}
const orderPaymentRequest = new OrderPaymentRequest(orderId, orderPayLoad.itemName, orderPayLoad.totalCost, orderPayLoad.quantity);
const paymentResult = yield ctx.callActivity(processPaymentActivity, orderPaymentRequest);
if (!paymentResult) {
const orderNotification: OrderNotification = {
message: `Payment for order ${orderId} failed`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return;
}
const updatedResult = yield ctx.callActivity(updateInventoryActivity, inventoryRequest);
if (!updatedResult.success) {
const orderNotification: OrderNotification = {
message: `Failed to update inventory for order ${orderId}`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return;
}
const orderCompletedNotification: OrderNotification = {
message: `order ${orderId} processed successfully!`,
};
yield ctx.callActivity(notifyActivity, orderCompletedNotification);
console.log(`Order ${orderId} processed successfully!`);
}

View File

@ -0,0 +1,62 @@
import { DaprWorkflowClient, WorkflowRuntime, DaprClient } from "@dapr/dapr-dev";
import { InventoryItem, OrderPayload } from "./model";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
async function start() {
// Update the gRPC client and worker to use a local address and port
const workflowClient = new DaprWorkflowClient();
const workflowWorker = new WorkflowRuntime();
const daprClient = new DaprClient();
const storeName = "statestore";
const inventory = new InventoryItem("item1", 100, 100);
const key = inventory.itemName;
await daprClient.state.save(storeName, [
{
key: key,
value: inventory,
}
]);
const order = new OrderPayload("item1", 100, 10);
workflowWorker
.registerWorkflow(orderProcessingWorkflow)
.registerActivity(notifyActivity)
.registerActivity(reserveInventoryActivity)
.registerActivity(requestApprovalActivity)
.registerActivity(processPaymentActivity)
.registerActivity(updateInventoryActivity);
// Wrap the worker startup in a try-catch block to handle any errors during startup
try {
await workflowWorker.start();
console.log("Workflow runtime started successfully");
} catch (error) {
console.error("Error starting workflow runtime:", error);
}
// Schedule a new orchestration
try {
const id = await workflowClient.scheduleNewWorkflow(orderProcessingWorkflow, order);
console.log(`Orchestration scheduled with ID: ${id}`);
// Wait for orchestration completion
const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30);
console.log(`Orchestration completed! Result: ${state?.serializedOutput}`);
} catch (error) {
console.error("Error scheduling or waiting for orchestration:", error);
throw error;
}
await workflowWorker.stop();
await workflowClient.stop();
}
start().catch((e) => {
console.error(e);
process.exit(1);
});

2610
workflows/javascript/sdk/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
{
"name": "dapr-example-workflow-authoring",
"version": "1.0.0",
"description": "An example utilizing the Dapr JS-SDK to author workflow",
"private": "true",
"scripts": {
"build": "npx tsc --outDir ./dist/",
"start:order-process": "npm run build && node dist/workflowApp.js",
"start:dapr:order-process": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 50001 --components-path ../../components npm run start:order-process"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/readline-sync": "^1.4.8",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
"dependencies": {
"@dapr/dapr-dev": "3.2.0-20240212030427-319e2fb",
"@types/node": "^18.16.3"
}
}

View File

@ -0,0 +1,71 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist" /* Redirect output structure to the directory. */,
"rootDir": "./order-processor" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -49,16 +49,11 @@ dapr run -f .
```
==========Begin the purchase of item:==========
To restock items, type 'restock'.
To exit workflow console app, type 'exit'.
Enter the name of one of the following items to order: paperclip, cars, computers: cars
How many cars would you like to purchase? 10
Starting order workflow, purchasing 10 of cars
INFO:NotifyActivity:Received order b903d749cd814e099f06ebf4a56a2f90 for 10 cars at $150000 !
INFO:VerifyInventoryActivity:Verifying inventory for order b903d749cd814e099f06ebf4a56a2f90 of 10 cars
INFO:VerifyInventoryActivity:There are 100 Cars available for purchase
INFO:RequestApprovalActivity:Requesting approval for payment of 150000 USD for 10 cars
(ID = b903d749cd814e099f06ebf4a56a2f90) requires approval. Approve? [Y/N] y
INFO:NotifyActivity:Payment for order b903d749cd814e099f06ebf4a56a2f90 has been approved!
INFO:ProcessPaymentActivity:Processing payment: b903d749cd814e099f06ebf4a56a2f90 for 10 cars at 150000 USD
INFO:ProcessPaymentActivity:Payment for request ID b903d749cd814e099f06ebf4a56a2f90 processed successfully