Skip to content

Latest commit

 

History

History
217 lines (167 loc) · 8.02 KB

durable-functions-external-events.md

File metadata and controls

217 lines (167 loc) · 8.02 KB
title description services author manager keywords ms.service ms.devlang ms.topic ms.date ms.author
Handling external events in Durable Functions - Azure
Learn how to handle external events in the Durable Functions extension for Azure Functions.
functions
kashimiz
jeconnoc
azure-functions
multiple
conceptual
10/23/2018
azfuncdf

Handling external events in Durable Functions (Azure Functions)

Orchestrator functions have the ability to wait and listen for external events. This feature of Durable Functions is often useful for handling human interaction or other external triggers.

Wait for events

The WaitForExternalEvent method allows an orchestrator function to asynchronously wait and listen for an external event. The listening orchestrator function declares the name of the event and the shape of the data it expects to receive.

C#

[FunctionName("BudgetApproval")]
public static async Task Run(
    [OrchestrationTrigger] DurableOrchestrationContext context)
{
    bool approved = await context.WaitForExternalEvent<bool>("Approval");
    if (approved)
    {
        // approval granted - do the approved action
    }
    else
    {
        // approval denied - send a notification
    }
}

JavaScript (Functions v2 only)

const df = require("durable-functions");

module.exports = df.orchestrator(function*(context) {
    const approved = yield context.df.waitForExternalEvent("Approval");
    if (approved) {
        // approval granted - do the approved action
    } else {
        // approval denied - send a notification
    }
});

The preceding example listens for a specific single event and takes action when it's received.

You can listen for multiple events concurrently, like in the following example, which waits for one of three possible event notifications.

C#

[FunctionName("Select")]
public static async Task Run(
    [OrchestrationTrigger] DurableOrchestrationContext context)
{
    var event1 = context.WaitForExternalEvent<float>("Event1");
    var event2 = context.WaitForExternalEvent<bool>("Event2");
    var event3 = context.WaitForExternalEvent<int>("Event3");

    var winner = await Task.WhenAny(event1, event2, event3);
    if (winner == event1)
    {
        // ...
    }
    else if (winner == event2)
    {
        // ...
    }
    else if (winner == event3)
    {
        // ...
    }
}

JavaScript (Functions v2 only)

const df = require("durable-functions");

module.exports = df.orchestrator(function*(context) {
    const event1 = context.df.waitForExternalEvent("Event1");
    const event2 = context.df.waitForExternalEvent("Event2");
    const event3 = context.df.waitForExternalEvent("Event3");

    const winner = yield context.df.Task.any([event1, event2, event3]);
    if (winner === event1) {
        // ...
    } else if (winner === event2) {
        // ...
    } else if (winner === event3) {
        // ...
    }
});

The previous example listens for any of multiple events. It's also possible to wait for all events.

C#

[FunctionName("NewBuildingPermit")]
public static async Task Run(
    [OrchestrationTrigger] DurableOrchestrationContext context)
{
    string applicationId = context.GetInput<string>();

    var gate1 = context.WaitForExternalEvent("CityPlanningApproval");
    var gate2 = context.WaitForExternalEvent("FireDeptApproval");
    var gate3 = context.WaitForExternalEvent("BuildingDeptApproval");

    // all three departments must grant approval before a permit can be issued
    await Task.WhenAll(gate1, gate2, gate3);

    await context.CallActivityAsync("IssueBuildingPermit", applicationId);
}

JavaScript (Functions v2 only)

const df = require("durable-functions");

module.exports = df.orchestrator(function*(context) {
    const applicationId = context.df.getInput();

    const gate1 = context.df.waitForExternalEvent("CityPlanningApproval");
    const gate2 = context.df.waitForExternalEvent("FireDeptApproval");
    const gate3 = context.df.waitForExternalEvent("BuildingDeptApproval");

    // all three departments must grant approval before a permit can be issued
    yield context.df.Task.all([gate1, gate2, gate3]);

    yield context.df.callActivity("IssueBuildingPermit", applicationId);
});

WaitForExternalEvent waits indefinitely for some input. The function app can be safely unloaded while waiting. If and when an event arrives for this orchestration instance, it is awakened automatically and immediately processes the event.

Note

If your function app uses the Consumption Plan, no billing charges are incurred while an orchestrator function is awaiting a task from WaitForExternalEvent, no matter how long it waits.

In .NET, if the event payload cannot be converted into the expected type T, an exception is thrown.

Send events

The RaiseEventAsync method of the DurableOrchestrationClient class sends the events that WaitForExternalEvent waits for. The RaiseEventAsync method takes eventName and eventData as parameters. The event data must be JSON-serializable.

Below is an example queue-triggered function that sends an "Approval" event to an orchestrator function instance. The orchestration instance ID comes from the body of the queue message.

[FunctionName("ApprovalQueueProcessor")]
public static async Task Run(
    [QueueTrigger("approval-queue")] string instanceId,
    [OrchestrationClient] DurableOrchestrationClient client)
{
    await client.RaiseEventAsync(instanceId, "Approval", true);
}

JavaScript (Functions v2 only)

In JavaScript we will have to invoke a rest api to trigger an event for which the durable function is waiting for. The code below uses the "request" package. The method below can be used to raise any event for any durable function instance

function raiseEvent(instanceId, eventName) {
        var url = `<<BASE_URL>>/runtime/webhooks/durabletask/instances/${instanceId}/raiseEvent/${eventName}?taskHub=DurableFunctionsHub`;
        var body = <<BODY>>
            
        return new Promise((resolve, reject) => {
            request({
                url,
                json: body,
                method: "POST"
            }, (e, response) => {
                if (e) {
                    return reject(e);
                }

                resolve();
            })
        });
    }

<<BASE_URL>> will be the base url of the your function app. If you are running code locally, then it will look something like http://localhost:7071 or in Azure as https://<>.azurewebsites.net

Internally, RaiseEventAsync enqueues a message that gets picked up by the waiting orchestrator function.

Warning

If there is no orchestration instance with the specified instance ID or if the instance is not waiting on the specified event name, the event message is discarded. For more information about this behavior, see the GitHub issue.

Next steps

[!div class="nextstepaction"] Learn how to set up eternal orchestrations

[!div class="nextstepaction"] Run a sample that waits for external events

[!div class="nextstepaction"] Run a sample that waits for human interaction