Serverless Workflows with Durable Functions

Serverless Workflows with Durable Functions

Serverless is the next evolution in cloud computing. Azure Functions are simple, short-lived, stateless, single-purpose actions that respond to events in the cloud.

But your applications are probably complex, multi-step, long-running, and stateful! Are you still able to reap the benefits of serverless? Do you have to hand-craft the interactions of stateless isolated functions yourself?

In this talk, Mikhail will demonstrate how Durable Functions combine Azure Functions and Durable Tasks to enable you to manage the state of workflows in the cloud. The toolbox includes "fan-in fan-out" pattern, waiting for external events and human interaction, error handling, retries and timeouts, and more.

6f1d9a240fc75d00c4a4ffa000ea4bd1?s=128

Mikhail Shilkov

April 09, 2019
Tweet

Transcript

  1. 2.

    Mikhail Shilkov Software Developer Microsoft Azure MVP Serverless Azure Functions

    Functional Programming F# @MikhailShilkov https://mikhail.io
  2. 6.
  3. 14.
  4. 17.
  5. 18.
  6. 19.
  7. 20.
  8. 23.
  9. 24.
  10. 25.
  11. 26.

    $

  12. 27.
  13. 29.
  14. 31.
  15. 36.
  16. 37.
  17. 38.
  18. 40.

    [FunctionName("SendText")] public static string SendText( [ActivityTrigger] string message) { var

    number = Config.alertMobileNumber; SmsService.Send(number, message); }
  19. 41.

    [FunctionName("SendText")] public static string SendText( [ActivityTrigger] string message) { var

    number = Config.alertMobileNumber; SmsService.Send(number, message); }
  20. 42.

    [FunctionName("SendText")] public static string SendText( [ActivityTrigger] string message) { var

    number = Config.alertMobileNumber; SmsService.Send(number, message); }
  21. 47.
  22. 48.
  23. 50.

    public static async Task Sequential(DurableOrchestrationContext context) { var text =

    await context.CallActivityAsync<string> ("SendText", "SMS Text"); var email = await context.CallActivityAsync<string> ("SendEmail", "Email Text"); var entry = new AlertEntry(text, email); await context.CallActivityAsync("RecordAlert", entry); }
  24. 52.

    1. Orchestrator Started 2. ExecutionStarted: RaiseAlert 3. TaskScheduled: SendText 4.

    OrchestratorCompleted 5. TaskCompleted: "Text sent at 11:33" 6. OrchestratorStarted 7. TaskScheduled: SendEmail 8. OrchestratorCompleted 9. TaskCompleted: “Email sent at 11:35"
  25. 53.

    public static async Task Sequential(DurableOrchestrationContext context) { var text =

    await context.CallActivityAsync<string> ("SendText", "SMS Text"); var email = await context.CallActivityAsync<string> ("SendEmail", "Email Text"); var entry = new AlertEntry(text, email); await context.CallActivityAsync("RecordAlert", entry); }
  26. 54.
  27. 55.
  28. 57.

    public static async Task SuperOrchestrator( DurableOrchestrationContext context) { await context.CallSubOrchestratorAsync

    ("RaiseAlert", serverOverloaded); await context.CallSubOrchestratorAsync ("RaiseAlert", applicationDown); }
  29. 58.
  30. 60.

    public static async Task ErrorHandling(DurableOrchestrationContext context) { var text =

    await context.CallActivityAsync<string>("SendText", "SMS Text"); string email = null; try { email = await context.CallActivityAsync<string>("SendEmail", "Email Text"); } catch (FunctionFailedException) { email = await context.CallActivityAsync<string>("SendAlternativeEmail", "Email"); } var entry = new AlertEntry(text, email); await context.CallActivityAsync("RecordAlert", entry); }
  31. 61.

    public static async Task ErrorHandling(DurableOrchestrationContext context) { var text =

    await context.CallActivityAsync<string>("SendText", "SMS Text"); string email = null; try { email = await context.CallActivityAsync<string>("SendEmail", "Email Text"); } catch (FunctionFailedException) { email = await context.CallActivityAsync<string>("SendAlternativeEmail", "Email"); } var entry = new AlertEntry(text, email); await context.CallActivityAsync("RecordAlert", entry); }
  32. 63.

    public static async Task Retries(DurableOrchestrationContext context) { var text =

    await context.CallActivityAsync<string>("SendText", "SMS Text"); var options = new RetryOptions( firstRetryInterval: TimeSpan.FromMinutes(1), maxNumberOfAttempts: 4); options.BackoffCoefficient = 2.0; await context.CallActivityWithRetryAsync("SendEmail", options, email); var entry = new AlertEntry(text, email); await context.CallActivityAsync("RecordAlert", entry); }
  33. 64.
  34. 66.

    public static async Task Parallel(DurableOrchestrationContext context) { var textTask =

    context.CallActivityAsync<string> ("SendText", "SMS Text"); var emailTask = context.CallActivityAsync<string> ("SendEmail", "Email Text"); var text = await textTask; var email = await emailTask; var entry = new AlertEntry(text, email); await context.CallActivityAsync("RecordAlert", entry); }
  35. 67.

    public static async Task Parallel(DurableOrchestrationContext cxt) { var tasks =

    alerts.Select(alert => cxt.CallActivityAsync<string>("SendText", alert)); var emails = Task.WhenAll(tasks); // ... }
  36. 68.
  37. 69.
  38. 70.

    var email = await context.CallActivityAsync<string>("SendEmail", "Email Text"); // Wait for

    10 minutes and check again var waitUntil = context.CurrentUtcDateTime.Date.Add(TimeSpan.FromMinutes(10)); await context.CreateTimer(waitUntil); var isResolved = await context.CallActivityAsync<bool>("CheckStatus", "Sensor"); if (!isResolved) { var text = await context.CallActivityAsync<string>("SendText", "SMS Text"); // Wait for result with timeout var activityTask = context.WaitForExternalEvent<bool>("Approval"); var timeoutTask = context.CreateTimer(deadline); // Determine the outcome var winner = await Task.WhenAny(activityTask, timeoutTask); return winner == activityTask && activityTask.Result; }
  39. 71.

    var email = await context.CallActivityAsync<string>("SendEmail", "Email Text"); // Wait for

    10 minutes and check again var waitUntil = context.CurrentUtcDateTime.Date.Add(TimeSpan.FromMinutes(10)); await context.CreateTimer(waitUntil); var isResolved = await context.CallActivityAsync<bool>("CheckStatus", "Sensor"); if (!isResolved) { var text = await context.CallActivityAsync<string>("SendText", "SMS Text"); // Wait for result with timeout var activityTask = context.WaitForExternalEvent<bool>("Approval"); var timeoutTask = context.CreateTimer(deadline); // Determine the outcome var winner = await Task.WhenAny(activityTask, timeoutTask); return winner == activityTask && activityTask.Result; }
  40. 72.

    var email = await context.CallActivityAsync<string>("SendEmail", "Email Text"); // Wait for

    10 minutes and check again var waitUntil = context.CurrentUtcDateTime.Date.Add(TimeSpan.FromMinutes(10)); await context.CreateTimer(waitUntil); var isResolved = await context.CallActivityAsync<bool>("CheckStatus", "Sensor"); if (!isResolved) { var text = await context.CallActivityAsync<string>("SendText", "SMS Text"); // Wait for result with timeout var activityTask = context.WaitForExternalEvent<bool>("Approval"); var timeoutTask = context.CreateTimer(deadline); // Determine the outcome var winner = await Task.WhenAny(activityTask, timeoutTask); return winner == activityTask && activityTask.Result; }
  41. 73.

    var email = await context.CallActivityAsync<string>("SendEmail", "Email Text"); // Wait for

    10 minutes and check again var waitUntil = context.CurrentUtcDateTime.Date.Add(TimeSpan.FromMinutes(10)); await context.CreateTimer(waitUntil); var isResolved = await context.CallActivityAsync<bool>("CheckStatus", "Sensor"); if (!isResolved) { var text = await context.CallActivityAsync<string>("SendText", "SMS Text"); // Wait for result with timeout var activityTask = context.WaitForExternalEvent<bool>("Approval"); var timeoutTask = context.CreateTimer(deadline); // Determine the outcome var winner = await Task.WhenAny(activityTask, timeoutTask); return winner == activityTask && activityTask.Result; }
  42. 74.

    var email = await context.CallActivityAsync<string>("SendEmail", "Email Text"); // Wait for

    10 minutes and check again var waitUntil = context.CurrentUtcDateTime.Date.Add(TimeSpan.FromMinutes(10)); await context.CreateTimer(waitUntil); var isResolved = await context.CallActivityAsync<bool>("CheckStatus", "Sensor"); if (!isResolved) { var text = await context.CallActivityAsync<string>("SendText", "SMS Text"); // Wait for result with timeout var activityTask = context.WaitForExternalEvent<bool>("Approval"); var timeoutTask = context.CreateTimer(deadline); // Determine the outcome var winner = await Task.WhenAny(activityTask, timeoutTask); return winner == activityTask && activityTask.Result; }
  43. 75.
  44. 76.

    // activity.js module.exports = function(context) { return `Hello ${context.bindings.name}!`; };

    // orchestrator.js const df = require("durable-functions"); module.exports = df.orchestrator(function*(context) { const output = []; output.push(yield context.df.callActivity("E1_SayHello", "Tokyo")); output.push(yield context.df.callActivity("E1_SayHello", "Seattle")); output.push(yield context.df.callActivity("E1_SayHello", "London")); return output; });
  45. 77.

    // Activity let sayHello = Activity.define "SayHello" (sprintf "Hello %s!")

    // Orchestrator let workflow = orchestrator { let! hello1 = Activity.call sayHello "Tokyo" let! hello2 = Activity.call sayHello "Seattle" let! hello3 = Activity.call sayHello "London" return [hello1; hello2; hello3] }
  46. 78.
  47. 80.
  48. 81.