Skip to content

Commit

Permalink
Merge branch 'master' into add-actor-serialization-options
Browse files Browse the repository at this point in the history
  • Loading branch information
halspang authored May 25, 2023
2 parents 8e6296a + a4f5fc0 commit a199879
Show file tree
Hide file tree
Showing 17 changed files with 155 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ info: WorkflowConsoleApp.Activities.NotifyActivity[0]

If you have Zipkin configured for Dapr locally on your machine, then you can view the workflow trace spans in the Zipkin web UI (typically at http://localhost:9411/zipkin/).

## Demo

Watch this video [demonstrating .NET Workflow](https://youtu.be/BxiKpEmchgQ?t=2557):

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/BxiKpEmchgQ?start=2557" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

## Next steps

- [Try the Dapr Workflow quickstart]({{< ref workflow-quickstart.md >}})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ namespace ConfigurationApi.Controllers
{
[ApiController]
[Route("configuration")]
[Obsolete]
public class ConfigurationController : ControllerBase
{
private ILogger<ConfigurationController> logger;
Expand Down
3 changes: 1 addition & 2 deletions examples/Client/ConfigurationApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Dapr.Client;
Expand All @@ -10,7 +10,6 @@ namespace ConfigurationApi
{
public class Program
{
[Obsolete]
public static void Main(string[] args)
{
Console.WriteLine("Starting application.");
Expand Down
47 changes: 21 additions & 26 deletions examples/Workflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,46 +49,43 @@ For the workflow API option, two identical `curl` commands are shown, one for Li
Make note of the "1234" in the commands below. This represents the unique identifier for the workflow run and can be replaced with any identifier of your choosing.

```bash
curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/1234/start \
curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=1234 \
-H "Content-Type: application/json" \
-d '{ "input" : {"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}}'
-d '{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}'
```

On Windows (PowerShell):

```powershell
curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/1234/start `
curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=1234 `
-H "Content-Type: application/json" `
-d '{ "input" : {"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}}'
-d '{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}'
```

If successful, you should see a response like the following:

```json
{"instance_id":"1234"}
{"instanceID":"1234"}
```

Next, send an HTTP request to get the status of the workflow that was started:

```bash
curl -i -X GET http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/1234
curl -i -X GET http://localhost:3500/v1.0-alpha1/workflows/dapr/1234
```

The workflow is designed to take several seconds to complete. If the workflow hasn't completed yet when you issue the previous command, you should see the following JSON response (formatted for readability):

```json
{
"WFInfo": {
"instance_id": "1234"
},
"start_time": "2023-02-02T23:34:53Z",
"metadata": {
"instanceID": "1234",
"workflowName": "OrderProcessingWorkflow",
"createdAt": "2023-05-10T00:42:03.911444105Z",
"lastUpdatedAt": "2023-05-10T00:42:06.142214153Z",
"runtimeStatus": "RUNNING",
"properties": {
"dapr.workflow.custom_status": "",
"dapr.workflow.input": "{\"Name\":\"Paperclips\",\"Quantity\":1,\"TotalCost\":99.95}",
"dapr.workflow.last_updated": "2023-02-02T23:35:07Z",
"dapr.workflow.name": "OrderProcessingWorkflow",
"dapr.workflow.output": "{\"Processed\":true}",
"dapr.workflow.runtime_status": "RUNNING"
"dapr.workflow.input": "{\"Name\": \"Paperclips\", \"TotalCost\": 99.95, \"Quantity\": 1}"
}
}
```
Expand All @@ -97,17 +94,15 @@ Once the workflow has completed running, you should see the following output, in

```json
{
"WFInfo": {
"instance_id": "1234"
},
"start_time": "2023-02-02T23:34:53Z",
"metadata": {
"instanceID": "1234",
"workflowName": "OrderProcessingWorkflow",
"createdAt": "2023-05-10T00:42:03.911444105Z",
"lastUpdatedAt": "2023-05-10T00:42:18.527704176Z",
"runtimeStatus": "COMPLETED",
"properties": {
"dapr.workflow.custom_status": "",
"dapr.workflow.input": "{\"Name\":\"Paperclips\",\"Quantity\":1,\"TotalCost\":99.95}",
"dapr.workflow.last_updated": "2023-02-02T23:35:07Z",
"dapr.workflow.name": "OrderProcessingWorkflow",
"dapr.workflow.output": "{\"Processed\":true}",
"dapr.workflow.runtime_status": "COMPLETED"
"dapr.workflow.input": "{\"Name\": \"Paperclips\", \"TotalCost\": 99.95, \"Quantity\": 1}",
"dapr.workflow.output": "{\"Processed\":true}"
}
}
```
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
using WorkflowConsoleApp.Models;

namespace WorkflowConsoleApp.Activities
{
public class RequestApprovalActivity : WorkflowActivity<OrderPayload, object>
{
readonly ILogger logger;

public RequestApprovalActivity(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.CreateLogger<RequestApprovalActivity>();
}

public override Task<object> RunAsync(WorkflowActivityContext context, OrderPayload input)
{
string orderId = context.InstanceId.ToString();
this.logger.LogInformation("Requesting approval for order {orderId}", orderId);

return Task.FromResult<object>(null);
}
}
}
6 changes: 6 additions & 0 deletions examples/Workflow/WorkflowConsoleApp/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@ public record InventoryResult(bool Success, InventoryItem orderPayload);
public record PaymentRequest(string RequestId, string ItemName, int Amount, double Currency);
public record OrderResult(bool Processed);
public record InventoryItem(string Name, double PerItemCost, int Quantity);
public enum ApprovalResult
{
Unspecified = 0,
Approved = 1,
Rejected = 2,
}
}
52 changes: 49 additions & 3 deletions examples/Workflow/WorkflowConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// These are the activities that get invoked by the workflow(s).
options.RegisterActivity<NotifyActivity>();
options.RegisterActivity<ReserveInventoryActivity>();
options.RegisterActivity<RequestApprovalActivity>();
options.RegisterActivity<ProcessPaymentActivity>();
options.RegisterActivity<UpdateInventoryActivity>();
});
Expand Down Expand Up @@ -126,9 +127,54 @@ await daprClient.StartWorkflowAsync(
Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) started successfully with {state.ReadInputAs<OrderPayload>()}");

// Wait for the workflow to complete
state = await daprClient.WaitForWorkflowCompletionAsync(
instanceId: orderId,
workflowComponent: DaprWorkflowComponent);
while (true)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
state = await daprClient.WaitForWorkflowCompletionAsync(
instanceId: orderId,
workflowComponent: DaprWorkflowComponent,
cancellationToken: cts.Token);
break;
}
catch (OperationCanceledException)
{
// Check to see if the workflow is blocked waiting for an approval
state = await daprClient.GetWorkflowAsync(
instanceId: orderId,
workflowComponent: DaprWorkflowComponent);
if (state.Properties.TryGetValue("dapr.workflow.custom_status", out string customStatus) &&
customStatus.Contains("Waiting for approval"))
{
Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) requires approval. Approve? [Y/N]");
string approval = Console.ReadLine();
ApprovalResult approvalResult = ApprovalResult.Unspecified;
if (string.Equals(approval, "Y", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Approving order...");
approvalResult = ApprovalResult.Approved;
}
else if (string.Equals(approval, "N", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Rejecting order...");
approvalResult = ApprovalResult.Rejected;
}

if (approvalResult != ApprovalResult.Unspecified)
{
// Raise the workflow event to the workflow
await daprClient.RaiseWorkflowEventAsync(
instanceId: orderId,
workflowComponent: DaprWorkflowComponent,
eventName: "ManagerApproval",
eventData: approvalResult);
}

// otherwise, keep waiting
}
}
}

if (state.RuntimeStatus == WorkflowRuntimeStatus.Completed)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,39 @@ await context.CallActivityAsync(
return new OrderResult(Processed: false);
}

// Require orders over a certain threshold to be approved
if (order.TotalCost > 50000)
{
// Request manager approval for the order
await context.CallActivityAsync(nameof(RequestApprovalActivity), order);

try
{
// Pause and wait for a manager to approve the order
context.SetCustomStatus("Waiting for approval");
ApprovalResult approvalResult = await context.WaitForExternalEventAsync<ApprovalResult>(
eventName: "ManagerApproval",
timeout: TimeSpan.FromSeconds(30));
context.SetCustomStatus($"Approval result: {approvalResult}");
if (approvalResult == ApprovalResult.Rejected)
{
// The order was rejected, end the workflow here
await context.CallActivityAsync(
nameof(NotifyActivity),
new Notification($"Order was rejected by approver"));
return new OrderResult(Processed: false);
}
}
catch (TaskCanceledException)
{
// An approval timeout results in automatic order cancellation
await context.CallActivityAsync(
nameof(NotifyActivity),
new Notification($"Cancelling order because it didn't receive an approval"));
return new OrderResult(Processed: false);
}
}

// There is enough inventory available so the user can purchase the item(s). Process their payment
await context.CallActivityAsync(
nameof(ProcessPaymentActivity),
Expand Down
12 changes: 9 additions & 3 deletions src/Dapr.Client/DaprClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,6 @@ public abstract Task<Dictionary<string, Dictionary<string, string>>> GetBulkSecr
/// <param name="metadata">Optional metadata that will be sent to the configuration store being queried.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task"/> containing a <see cref="GetConfigurationResponse"/></returns>
[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<GetConfigurationResponse> GetConfiguration(
string storeName,
IReadOnlyList<string> keys,
Expand All @@ -922,7 +921,6 @@ public abstract Task<GetConfigurationResponse> GetConfiguration(
/// <param name="metadata">Optional metadata that will be sent to the configuration store being queried.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="SubscribeConfigurationResponse"/> which contains a reference to the stream.</returns>
[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<SubscribeConfigurationResponse> SubscribeConfiguration(
string storeName,
IReadOnlyList<string> keys,
Expand All @@ -936,7 +934,6 @@ public abstract Task<SubscribeConfigurationResponse> SubscribeConfiguration(
/// <param name="id">The Id of the subscription that should no longer be watched.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns></returns>
[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<UnsubscribeConfigurationResponse> UnsubscribeConfiguration(
string storeName,
string id,
Expand Down Expand Up @@ -1011,6 +1008,9 @@ public abstract Task<StartWorkflowResponse> StartWorkflowAsync(
/// <returns>
/// Returns a <see cref="GetWorkflowResponse"/> record that describes the workflow instance and its execution status.
/// </returns>
/// <exception cref="OperationCanceledException">
/// Thrown if <paramref name="cancellationToken"/> is canceled before the workflow starts running.
/// </exception>
[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public virtual async Task<GetWorkflowResponse> WaitForWorkflowStartAsync(
string instanceId,
Expand Down Expand Up @@ -1047,6 +1047,12 @@ public virtual async Task<GetWorkflowResponse> WaitForWorkflowStartAsync(
/// If a workflow instance is already complete when this method is called, the method will return immediately.
/// </para>
/// </remarks>
/// <returns>
/// Returns a <see cref="GetWorkflowResponse"/> record that describes the workflow instance and its execution status.
/// </returns>
/// <exception cref="OperationCanceledException">
/// Thrown if <paramref name="cancellationToken"/> is canceled before the workflow completes.
/// </exception>
[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public virtual async Task<GetWorkflowResponse> WaitForWorkflowCompletionAsync(
string instanceId,
Expand Down
3 changes: 0 additions & 3 deletions src/Dapr.Client/DaprClientGrpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,6 @@ public async override Task<Dictionary<string, Dictionary<string, string>>> GetBu

#region Configuration API
/// <inheritdoc/>
[Obsolete]
public async override Task<GetConfigurationResponse> GetConfiguration(
string storeName,
IReadOnlyList<string> keys,
Expand Down Expand Up @@ -1330,7 +1329,6 @@ public async override Task<GetConfigurationResponse> GetConfiguration(
}

/// <inheritdoc/>
[Obsolete]
public override Task<SubscribeConfigurationResponse> SubscribeConfiguration(
string storeName,
IReadOnlyList<string> keys,
Expand Down Expand Up @@ -1361,7 +1359,6 @@ public override Task<SubscribeConfigurationResponse> SubscribeConfiguration(
return Task.FromResult(new SubscribeConfigurationResponse(new DaprSubscribeConfigurationSource(client.SubscribeConfiguration(request, options))));
}

[Obsolete]
public override async Task<UnsubscribeConfigurationResponse> UnsubscribeConfiguration(
string storeName,
string id,
Expand Down
1 change: 0 additions & 1 deletion src/Dapr.Client/SubscribeConfigurationResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace Dapr.Client
/// <summary>
/// Response for a Subscribe Configuration request.
/// </summary>
[Obsolete("This response utilizes an alpha API which is subject to change. This attribute will be removed when the API is no longer Alpha.")]
public class SubscribeConfigurationResponse
{
private ConfigurationSource source;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ namespace Dapr.Extensions.Configuration
/// <summary>
/// Extension used to call the Dapr Configuration API and store the values in a <see cref="IConfiguration"/>.
/// </summary>
[Obsolete]
public static class DaprConfigurationStoreExtension
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ namespace Dapr.Extensions.Configuration
/// A configuration provider that utilizes the Dapr Configuration API. It can either be a single, constant
/// call or a streaming call.
/// </summary>
[Obsolete]
internal class DaprConfigurationStoreProvider : ConfigurationProvider, IDisposable
{
private string store;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ namespace Dapr.Extensions.Configuration
/// <summary>
/// Configuration source that provides a <see cref="DaprConfigurationStoreProvider"/>.
/// </summary>
[Obsolete]
public class DaprConfigurationStoreSource : IConfigurationSource
{
/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Dapr.Workflow/WorkflowContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ public virtual Task CreateTimer(TimeSpan delay, CancellationToken cancellationTo
/// <returns>
/// A task that completes when the external event is received. The value of the task is the deserialized event payload.
/// </returns>
/// <exception cref="TaskCanceledException">
/// Thrown if <paramref name="cancellationToken"/> is cancelled before the external event is received.
/// </exception>
/// <exception cref="InvalidOperationException">
/// Thrown if the calling thread is not the workflow dispatch thread.
/// </exception>
Expand All @@ -172,6 +175,9 @@ public virtual Task CreateTimer(TimeSpan delay, CancellationToken cancellationTo
/// number of times; they are not required to be unique.
/// </param>
/// <param name="timeout">The amount of time to wait before cancelling the external event task.</param>
/// <exception cref="TaskCanceledException">
/// Thrown if <paramref name="timeout"/> elapses before the external event is received.
/// </exception>
/// <inheritdoc cref="WaitForExternalEventAsync{T}(string, CancellationToken)"/>
public abstract Task<T> WaitForExternalEventAsync<T>(string eventName, TimeSpan timeout);

Expand Down
1 change: 0 additions & 1 deletion test/Dapr.Client.Test/ConfigurationApiTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

namespace Dapr.Client.Test
{
[System.Obsolete]
public class ConfigurationApiTest
{
[Fact]
Expand Down
Loading

0 comments on commit a199879

Please sign in to comment.