mirror of https://github.com/dapr/quickstarts.git
				
				
				
			add quickstart for JS
Signed-off-by: kaibocai <kaibocai@microsoft.com> clean up Signed-off-by: kaibocai <kaibocai@microsoft.com> update .gitignore fix .gitignore Signed-off-by: kaibocai <kaibocai@microsoft.com>
This commit is contained in:
		
							parent
							
								
									ec1e515512
								
							
						
					
					
						commit
						2d7b401e83
					
				| 
						 | 
				
			
			@ -18,6 +18,7 @@ release.properties
 | 
			
		|||
mvnw
 | 
			
		||||
packages
 | 
			
		||||
**/__pycache__/
 | 
			
		||||
**/dist/
 | 
			
		||||
 | 
			
		||||
# IDE generated files and directories
 | 
			
		||||
*.iml
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
cd ..
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<!-- END_STEP -->
 | 
			
		||||
2. Run the console app with Dapr: 
 | 
			
		||||
 | 
			
		||||
<!-- STEP
 | 
			
		||||
name: Run order-processor service
 | 
			
		||||
expected_stdout_lines:
 | 
			
		||||
  - '== APP - there are now 90 item1 in stock'
 | 
			
		||||
  - '== APP - 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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
version: 1
 | 
			
		||||
common:
 | 
			
		||||
  resourcesPath: ../../components
 | 
			
		||||
apps:
 | 
			
		||||
  - appID: workflowApp
 | 
			
		||||
    appDirPath: ./order-processor/
 | 
			
		||||
    command: ["npm", "run", "start:dapr:order-process"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
include ../../../docker.mk
 | 
			
		||||
include ../../../validate.mk
 | 
			
		||||
| 
						 | 
				
			
			@ -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})`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 Verify 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!`);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
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 daprHost = "localhost";
 | 
			
		||||
  const daprPort = "50001";
 | 
			
		||||
  const workflowClient = new DaprWorkflowClient({
 | 
			
		||||
    daprHost,
 | 
			
		||||
    daprPort,
 | 
			
		||||
  });
 | 
			
		||||
  const workflowRuntime = new WorkflowRuntime({
 | 
			
		||||
    daprHost,
 | 
			
		||||
    daprPort,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  workflowRuntime
 | 
			
		||||
  .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 workflowRuntime.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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  await workflowRuntime.stop();
 | 
			
		||||
  await workflowClient.stop();
 | 
			
		||||
 | 
			
		||||
  // stop the dapr side car
 | 
			
		||||
  process.exit(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
start().catch((e) => {
 | 
			
		||||
  console.error(e);
 | 
			
		||||
  process.exit(1);
 | 
			
		||||
});
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -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"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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. */
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue