mirror of https://github.com/dapr/quickstarts.git
Merge branch 'release-1.13' of github.com:dapr/quickstarts into release-1.13
This commit is contained in:
commit
a35571fc9a
|
@ -18,6 +18,7 @@ release.properties
|
||||||
mvnw
|
mvnw
|
||||||
packages
|
packages
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
|
**/dist/
|
||||||
Debug/
|
Debug/
|
||||||
|
|
||||||
# IDE generated files and directories
|
# IDE generated files and directories
|
||||||
|
|
|
@ -36,7 +36,7 @@ internal class SmokeDetectorActor : Actor, ISmartDevice
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override Task OnDeactivateAsync()
|
protected override Task OnDeactivateAsync()
|
||||||
{
|
{
|
||||||
// Provides Opportunity to perform optional cleanup.
|
// Provides opportunity to perform optional cleanup.
|
||||||
Console.WriteLine($"Deactivating actor id: {Id}");
|
Console.WriteLine($"Deactivating actor id: {Id}");
|
||||||
return Task.CompletedTask;
|
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>
|
/// <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)
|
public async Task<string> SetDataAsync(SmartDeviceData data)
|
||||||
{
|
{
|
||||||
// Data is saved to configured state store *implicitly* after each method execution by Actor's runtime.
|
// This set state action can happen along other state changing operations in each actor method and those changes will be maintained
|
||||||
// Data can also be saved *explicitly* by calling this.StateManager.SaveStateAsync();
|
// in a local cache to be committed as a single transaction to the backing store when the method has completed. As such, there is
|
||||||
// State to be saved must be DataContract serializable.
|
// 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>(
|
await StateManager.SetStateAsync<SmartDeviceData>(
|
||||||
deviceDataKey,
|
deviceDataKey,
|
||||||
data);
|
data);
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Dapr.Client;
|
||||||
|
|
||||||
var baseURL = (Environment.GetEnvironmentVariable("BASE_URL") ?? "http://localhost") + ":" + (Environment.GetEnvironmentVariable("DAPR_HTTP_PORT") ?? "3500");
|
var client = DaprClient.CreateInvokeHttpClient(appId: "order-processor");
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
for (int i = 1; i <= 20; i++) {
|
for (int i = 1; i <= 20; i++) {
|
||||||
var order = new Order(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
|
// 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);
|
Console.WriteLine("Order passed: " + order);
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||||
|
|
|
@ -7,4 +7,9 @@
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapr.Client" Version="1.12.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -74,8 +74,8 @@ sleep: 15
|
||||||
cd ./order-processor
|
cd ./order-processor
|
||||||
dapr run --app-id order-processor --resources-path ../../../resources/ -- dotnet run
|
dapr run --app-id order-processor --resources-path ../../../resources/ -- dotnet run
|
||||||
```
|
```
|
||||||
<!-- END_STEP -->
|
|
||||||
|
|
||||||
2. Stop and clean up application processes
|
2. Stop and clean up application processes
|
||||||
|
|
||||||
dapr stop --app-id order-processor
|
dapr stop --app-id order-processor
|
||||||
|
<!-- END_STEP -->
|
|
@ -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 == Getting Order: Order { orderId = 3 }
|
||||||
== APP == Deleting Order: Order { orderId = 3 }
|
== APP == Deleting Order: Order { orderId = 3 }
|
||||||
```
|
```
|
||||||
<!-- END_STEP -->
|
|
||||||
|
|
||||||
2. Stop and clean up application processes
|
2. Stop and clean up application processes
|
||||||
|
|
||||||
```bash
|
|
||||||
dapr stop --app-id order-processor
|
dapr stop --app-id order-processor
|
||||||
```
|
<!-- END_STEP -->
|
|
@ -79,9 +79,8 @@ You're up and running! Both Dapr and your app logs will appear here.
|
||||||
== APP == Retrieved Order: "{\"orderId\":3}"
|
== APP == Retrieved Order: "{\"orderId\":3}"
|
||||||
== APP == 2023/09/24 23:31:27 Deleted Order: {"orderId":3}
|
== APP == 2023/09/24 23:31:27 Deleted Order: {"orderId":3}
|
||||||
```
|
```
|
||||||
<!-- END_STEP -->
|
|
||||||
|
|
||||||
2. Stop and clean up application processes
|
2. Stop and clean up application processes
|
||||||
```bash
|
|
||||||
dapr stop --app-id order-processor
|
dapr stop --app-id order-processor
|
||||||
```
|
|
||||||
|
<!-- END_STEP -->
|
||||||
|
|
|
@ -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 == Retrieved Order: {"orderId":2}
|
||||||
== APP - order-processor == Deleted Order: {"orderId":2}
|
== APP - order-processor == Deleted Order: {"orderId":2}
|
||||||
```
|
```
|
||||||
<!-- END_STEP -->
|
|
||||||
|
|
||||||
2. Stop and clean up application processes
|
2. Stop and clean up application processes
|
||||||
```bash
|
|
||||||
dapr stop --app-id order-processor
|
dapr stop --app-id order-processor
|
||||||
```
|
|
||||||
|
<!-- END_STEP -->
|
||||||
|
|
|
@ -88,11 +88,10 @@ sleep: 60
|
||||||
```bash
|
```bash
|
||||||
dapr run --app-id order-processor --resources-path ../../../resources/ -- npm start
|
dapr run --app-id order-processor --resources-path ../../../resources/ -- npm start
|
||||||
```
|
```
|
||||||
<!-- END_STEP -->
|
|
||||||
|
|
||||||
2. Stop and cleanup the process
|
2. Stop and cleanup the process
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dapr stop --app-id order-processor
|
dapr stop --app-id order-processor
|
||||||
```
|
```
|
||||||
|
<!-- END_STEP -->
|
||||||
|
|
|
@ -22,7 +22,7 @@ app.use(bodyParser.json());
|
||||||
const daprPort = process.env.DAPR_HTTP_PORT ?? "3500";
|
const daprPort = process.env.DAPR_HTTP_PORT ?? "3500";
|
||||||
const daprGRPCPort = process.env.DAPR_GRPC_PORT ?? "50001";
|
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 stateUrl = `http://localhost:${daprPort}/v1.0/state/${stateStoreName}`;
|
||||||
const port = process.env.APP_PORT ?? "3000";
|
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 })
|
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}!`));
|
||||||
|
|
|
@ -33,9 +33,7 @@ host.Start();
|
||||||
|
|
||||||
using var daprClient = new DaprClientBuilder().Build();
|
using var daprClient = new DaprClientBuilder().Build();
|
||||||
|
|
||||||
// NOTE: WorkflowEngineClient will be replaced with a richer version of DaprClient
|
DaprWorkflowClient workflowClient = host.Services.GetRequiredService<DaprWorkflowClient>();
|
||||||
// in a subsequent SDK release. This is a temporary workaround.
|
|
||||||
WorkflowEngineClient workflowClient = host.Services.GetRequiredService<WorkflowEngineClient>();
|
|
||||||
|
|
||||||
// Generate a unique ID for the workflow
|
// Generate a unique ID for the workflow
|
||||||
string orderId = Guid.NewGuid().ToString()[..8];
|
string orderId = Guid.NewGuid().ToString()[..8];
|
||||||
|
@ -51,28 +49,25 @@ OrderPayload orderInfo = new OrderPayload(itemToPurchase, 15000, ammountToPurcha
|
||||||
// Start the workflow
|
// Start the workflow
|
||||||
Console.WriteLine("Starting workflow {0} purchasing {1} {2}", orderId, ammountToPurchase, itemToPurchase);
|
Console.WriteLine("Starting workflow {0} purchasing {1} {2}", orderId, ammountToPurchase, itemToPurchase);
|
||||||
|
|
||||||
await daprClient.StartWorkflowAsync(
|
await workflowClient.ScheduleNewWorkflowAsync(
|
||||||
workflowComponent: DaprWorkflowComponent,
|
name: nameof(OrderProcessingWorkflow),
|
||||||
workflowName: nameof(OrderProcessingWorkflow),
|
instanceId: orderId,
|
||||||
input: orderInfo,
|
input: orderInfo);
|
||||||
instanceId: orderId);
|
|
||||||
|
|
||||||
// Wait for the workflow to start and confirm the input
|
// Wait for the workflow to start and confirm the input
|
||||||
GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync(
|
WorkflowState state = await workflowClient.WaitForWorkflowStartAsync(
|
||||||
instanceId: orderId,
|
instanceId: orderId);
|
||||||
workflowComponent: DaprWorkflowComponent);
|
|
||||||
|
|
||||||
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
|
// Wait for the workflow to complete
|
||||||
state = await daprClient.WaitForWorkflowCompletionAsync(
|
state = await workflowClient.WaitForWorkflowCompletionAsync(
|
||||||
instanceId: orderId,
|
instanceId: orderId);
|
||||||
workflowComponent: DaprWorkflowComponent);
|
|
||||||
|
|
||||||
Console.WriteLine("Workflow Status: {0}", state.RuntimeStatus);
|
Console.WriteLine("Workflow Status: {0}", Enum.GetName(typeof(WorkflowRuntimeStatus), state.RuntimeStatus));
|
||||||
|
|
||||||
void RestockInventory(string itemToPurchase)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 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!`);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
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. */
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,16 +49,11 @@ dapr run -f .
|
||||||
|
|
||||||
```
|
```
|
||||||
==========Begin the purchase of item:==========
|
==========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
|
Starting order workflow, purchasing 10 of cars
|
||||||
INFO:NotifyActivity:Received order b903d749cd814e099f06ebf4a56a2f90 for 10 cars at $150000 !
|
INFO:NotifyActivity:Received order b903d749cd814e099f06ebf4a56a2f90 for 10 cars at $150000 !
|
||||||
INFO:VerifyInventoryActivity:Verifying inventory for order b903d749cd814e099f06ebf4a56a2f90 of 10 cars
|
INFO:VerifyInventoryActivity:Verifying inventory for order b903d749cd814e099f06ebf4a56a2f90 of 10 cars
|
||||||
INFO:VerifyInventoryActivity:There are 100 Cars available for purchase
|
INFO:VerifyInventoryActivity:There are 100 Cars available for purchase
|
||||||
INFO:RequestApprovalActivity:Requesting approval for payment of 150000 USD for 10 cars
|
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:NotifyActivity:Payment for order b903d749cd814e099f06ebf4a56a2f90 has been approved!
|
||||||
INFO:ProcessPaymentActivity:Processing payment: b903d749cd814e099f06ebf4a56a2f90 for 10 cars at 150000 USD
|
INFO:ProcessPaymentActivity:Processing payment: b903d749cd814e099f06ebf4a56a2f90 for 10 cars at 150000 USD
|
||||||
INFO:ProcessPaymentActivity:Payment for request ID b903d749cd814e099f06ebf4a56a2f90 processed successfully
|
INFO:ProcessPaymentActivity:Payment for request ID b903d749cd814e099f06ebf4a56a2f90 processed successfully
|
||||||
|
|
Loading…
Reference in New Issue