How to: Author a workflow

Learn how to develop and author workflows

This article provides a high-level overview of how to author workflows that are executed by the Dapr Workflow engine.

Note

If you haven’t already, try out the workflow quickstart for a quick walk-through on how to use workflows.

Author workflows as code

Dapr Workflow logic is implemented using general purpose programming languages, allowing you to:

  • Use your preferred programming language (no need to learn a new DSL or YAML schema).
  • Have access to the language’s standard libraries.
  • Build your own libraries and abstractions.
  • Use debuggers and examine local variables.
  • Write unit tests for your workflows, just like any other part of your application logic.

The Dapr sidecar doesn’t load any workflow definitions. Rather, the sidecar simply drives the execution of the workflows, leaving all the workflow activities to be part of the application. The Dapr sidecar doesn’t load any workflow definitions. Rather, the sidecar simply drives the execution of the workflows, leaving all the workflow activitie to be part of the application.

Write the workflow activities

Define the workflow activities you’d like your workflow to perform. Activities are a class definition and can take inputs and outputs. Activities also participate in dependency injection, like binding to a Dapr client.

Continuing the ASP.NET order processing example, the OrderProcessingWorkflow class is derived from a base class called Workflow with input and output parameter types.

It also includes a RunAsync method that does the heavy lifting of the workflow and calls the workflow activities. The activities called in the example are:

  • NotifyActivity: Receive notification of a new order.
  • ReserveInventoryActivity: Check for sufficient inventory to meet the new order.
  • ProcessPaymentActivity: Process payment for the order. Includes NotifyActivity to send notification of successful order.
  1. class OrderProcessingWorkflow : Workflow<OrderPayload, OrderResult>
  2. {
  3. public override async Task<OrderResult> RunAsync(WorkflowContext context, OrderPayload order)
  4. {
  5. //...
  6. await context.CallActivityAsync(
  7. nameof(NotifyActivity),
  8. new Notification($"Received order {orderId} for {order.Name} at {order.TotalCost:c}"));
  9. //...
  10. InventoryResult result = await context.CallActivityAsync<InventoryResult>(
  11. nameof(ReserveInventoryActivity),
  12. new InventoryRequest(RequestId: orderId, order.Name, order.Quantity));
  13. //...
  14. await context.CallActivityAsync(
  15. nameof(ProcessPaymentActivity),
  16. new PaymentRequest(RequestId: orderId, order.TotalCost, "USD"));
  17. await context.CallActivityAsync(
  18. nameof(NotifyActivity),
  19. new Notification($"Order {orderId} processed successfully!"));
  20. // End the workflow with a success result
  21. return new OrderResult(Processed: true);
  22. }
  23. }

Write the workflow

Compose the workflow activities into a workflow.

In the following example, for a basic ASP.NET order processing application using the .NET SDK, your project code would include:

  • A NuGet package called Dapr.Workflow to receive the .NET SDK capabilities
  • A builder with an extension method called AddDaprWorkflow
    • This will allow you to register workflows and workflow activities (tasks that workflows can schedule)
  • HTTP API calls
    • One for submitting a new order
    • One for checking the status of an existing order
  1. using Dapr.Workflow;
  2. //...
  3. // Dapr Workflows are registered as part of the service configuration
  4. builder.Services.AddDaprWorkflow(options =>
  5. {
  6. // Note that it's also possible to register a lambda function as the workflow
  7. // or activity implementation instead of a class.
  8. options.RegisterWorkflow<OrderProcessingWorkflow>();
  9. // These are the activities that get invoked by the workflow(s).
  10. options.RegisterActivity<NotifyActivity>();
  11. options.RegisterActivity<ReserveInventoryActivity>();
  12. options.RegisterActivity<ProcessPaymentActivity>();
  13. });
  14. WebApplication app = builder.Build();
  15. // POST starts new order workflow instance
  16. app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) =>
  17. {
  18. if (orderInfo?.Name == null)
  19. {
  20. return Results.BadRequest(new
  21. {
  22. message = "Order data was missing from the request",
  23. example = new OrderPayload("Paperclips", 99.95),
  24. });
  25. }
  26. //...
  27. });
  28. // GET fetches state for order workflow to report status
  29. app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) =>
  30. {
  31. WorkflowState state = await client.GetWorkflowStateAsync(orderId, true);
  32. if (!state.Exists)
  33. {
  34. return Results.NotFound($"No order with ID = '{orderId}' was found.");
  35. }
  36. var httpResponsePayload = new
  37. {
  38. details = state.ReadInputAs<OrderPayload>(),
  39. status = state.RuntimeStatus.ToString(),
  40. result = state.ReadOutputAs<OrderResult>(),
  41. };
  42. //...
  43. }).WithName("GetOrderInfoEndpoint");
  44. app.Run();

Important

Because of how replay-based workflows execute, you’ll write logic that does things like I/O and interacting with systems inside activities. Meanwhile, the workflow method is just for orchestrating those activities.

Next steps

Now that you’ve authored a workflow, learn how to manage it.

Manage workflows >>

Last modified February 10, 2023: fix link (14f07709)