Teilen über


Überwachungsszenario in Durable Functions – Beispiel einer Wetterbeobachtungsstation

Das Monitormuster bezieht sich auf einen flexiblen wiederkehrenden Prozess in einem Workflow – wenn beispielsweise bestimmte Elemente solange abgerufen werden, bis bestimmte Bedingungen erfüllt sind. In diesem Artikel wird ein Beispiel beschrieben, in dem Durable Functions zum Implementieren der Überwachung verwendet wird.

Voraussetzungen

Übersicht über das Szenario

Dieses Beispiel überwacht die aktuellen Wetterbedingungen an einem bestimmten Ort und benachrichtigt einen Benutzer per SMS, wenn das Wetter schön ist. Sie könnten eine reguläre, per Timer ausgelöste Funktion verwenden, um das Wetter zu beobachten und Benachrichtigungen zu senden. Ein Problem bei diesem Ansatz ist jedoch die Lebensdauerverwaltung. Wenn nur eine einzige Benachrichtigung gesendet werden soll, muss der Monitor sich selbst deaktivieren, nachdem schönes Wetter erkannt wurde. Das Überwachungsmuster bietet neben weiteren Vorteilen den Vorzug, dass es die eigene Ausführung beenden kann:

  • Monitore werden in Intervallen, nicht nach Zeitplänen ausgeführt: Ein Timertrigger wird jede Stunde ausgeführt; ein Monitor wartet zwischen Aktionen eine Stunde. Die Aktionen eines Monitors überschneiden sich nicht, sofern dies nicht explizit angegeben wird. Dies kann bei Tasks mit langer Ausführungsdauer wichtig sein.
  • Monitore können in dynamischen Intervallen ausgeführt werden: Die Wartezeit kann sich aufgrund einer Bedingung ändern.
  • Monitore können beendet werden, wenn eine bestimmte Bedingung erfüllt ist. Sie können auch von einem anderen Prozess beendet werden.
  • Für Monitore können Parameter angegeben werden. Das Beispiel zeigt, wie ein und derselbe Wetterbeobachtungsprozess auf jeden gewünschten Standort und jede gewünschte Telefonnummer angewendet werden kann.
  • Monitore sind skalierbar. Da jeder Monitor eine Orchestrierungsinstanz ist, können mehrere Monitore erstellt werden, ohne neue Funktionen erstellen oder weiteren Code definieren zu müssen.
  • Monitore lassen sich problemlos in größere Workflows integrieren. Ein Monitor kann Teil einer komplexeren Orchestrierungsfunktion oder eine untergeordnete Orchestrierung sein.

Konfiguration

Konfigurieren der Twilio-Integration

Dieses Beispiel beinhaltet die Verwendung des Twilio-Diensts zum Senden von SMS-Nachrichten an ein Mobiltelefon. Azure Functions bietet über die Twilio-Bindung bereits Unterstützung für Twilio an. Dieses Feature wird in dem Beispiel verwendet.

Zunächst benötigen Sie ein Twilio-Konto. Unter https://www.twilio.com/try-twilio können Sie kostenlos eines erstellen. Fügen Sie nach der Einrichtung Ihres Kontos die folgenden drei App-Einstellungen zu Ihrer Funktions-App hinzu.

Name der App-Einstellung Wertbeschreibung
TwilioAccountSid Die SID für Ihr Twilio-Konto
TwilioAuthToken Das Authentifizierungstoken für Ihr Twilio-Konto
TwilioPhoneNumber Die Ihrem Twilio-Konto zugeordnete Telefonnummer. Diese wird für das Senden von SMS-Nachrichten verwendet.

Konfigurieren der Integration von Weather Underground

In diesem Beispiel wird die Weather Underground-API verwenden, um die Wetterbedingungen an einem Standort zu beobachten.

Als Erstes benötigen Sie ein Weather Underground-Konto. Unter https://www.wunderground.com/signup können Sie kostenlos eines erstellen. Sobald Sie über das Konto verfügen, müssen Sie einen API-Schlüssel abrufen. Öffnen Sie dazu die Website https://www.wunderground.com/weather/api, und wählen Sie „Key Settings“ (Schlüsseleinstellungen) aus. Der Stratus Developer-Plan ist kostenlos und für dieses Beispiel ausreichend.

Wenn Sie den API-Schlüssel haben, fügen Sie die folgenden App-Einstellungen zu Ihrer Funktions-App hinzu.

Name der App-Einstellung Wertbeschreibung
WeatherUndergroundApiKey Ihr Weather Underground-API-Schlüssel.

Die Funktionen

In diesem Artikel werden die folgenden Funktionen in der Beispiel-App beschrieben:

  • E3_Monitor: Eine Orchestratorfunktion, die E3_GetIsClear in regelmäßigen Abständen aufruft. Sie ruft E3_SendGoodWeatherAlert auf, wenn E3_GetIsClear „true“ zurückgibt.
  • E3_GetIsClear: Eine Aktivitätsfunktion, die die aktuellen Wetterbedingungen an einem Ort beobachtet.
  • E3_SendGoodWeatherAlert: Eine Aktivitätsfunktion, die eine SMS-Nachricht über Twilio sendet.

Orchestratorfunktion „E3_Monitor“

[FunctionName("E3_Monitor")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext monitorContext, ILogger log)
{
    MonitorRequest input = monitorContext.GetInput<MonitorRequest>();
    if (!monitorContext.IsReplaying) { log.LogInformation($"Received monitor request. Location: {input?.Location}. Phone: {input?.Phone}."); }

    VerifyRequest(input);

    DateTime endTime = monitorContext.CurrentUtcDateTime.AddHours(6);
    if (!monitorContext.IsReplaying) { log.LogInformation($"Instantiating monitor for {input.Location}. Expires: {endTime}."); }

    while (monitorContext.CurrentUtcDateTime < endTime)
    {
        // Check the weather
        if (!monitorContext.IsReplaying) { log.LogInformation($"Checking current weather conditions for {input.Location} at {monitorContext.CurrentUtcDateTime}."); }

        bool isClear = await monitorContext.CallActivityAsync<bool>("E3_GetIsClear", input.Location);

        if (isClear)
        {
            // It's not raining! Or snowing. Or misting. Tell our user to take advantage of it.
            if (!monitorContext.IsReplaying) { log.LogInformation($"Detected clear weather for {input.Location}. Notifying {input.Phone}."); }

            await monitorContext.CallActivityAsync("E3_SendGoodWeatherAlert", input.Phone);
            break;
        }
        else
        {
            // Wait for the next checkpoint
            var nextCheckpoint = monitorContext.CurrentUtcDateTime.AddMinutes(30);
            if (!monitorContext.IsReplaying) { log.LogInformation($"Next check for {input.Location} at {nextCheckpoint}."); }

            await monitorContext.CreateTimer(nextCheckpoint, CancellationToken.None);
        }
    }

    log.LogInformation($"Monitor expiring.");
}

[Deterministic]
private static void VerifyRequest(MonitorRequest request)
{
    if (request == null)
    {
        throw new ArgumentNullException(nameof(request), "An input object is required.");
    }

    if (request.Location == null)
    {
        throw new ArgumentNullException(nameof(request.Location), "A location input is required.");
    }

    if (string.IsNullOrEmpty(request.Phone))
    {
        throw new ArgumentNullException(nameof(request.Phone), "A phone number input is required.");
    }
}

Der Orchestrator benötigt einen zu überwachenden Standort sowie eine Telefonnummer, an die eine Nachricht gesendet wird, wenn das Wetter an diesem Ort aufklart. Diese Daten werden als stark typisiertes MonitorRequest-Objekt an den Orchestrator übergeben.

Diese Orchestratorfunktion führt die folgenden Aktionen aus:

  1. Sie ruft die MonitorRequest-Anforderung ab, die aus dem zu beobachtenden Standort und der Telefonnummer besteht, an die eine SMS-Benachrichtigung gesendet werden soll.
  2. Sie bestimmt den Ablaufzeitpunkt des Monitors. Aus Gründen der Übersichtlichkeit verwendet dieses Beispiel einen hartcodierten Wert.
  3. Sie ruft E3_GetIsClear auf, um zu ermitteln, ob das Wetter am angeforderten Standort schön ist.
  4. Wenn dies der Fall ist, ruft die Funktion E3_SendGoodWeatherAlert auf, um eine SMS-Benachrichtigung an die angeforderte Telefonnummer zu senden.
  5. Sie erstellt einen permanenten Timer, um die Orchestrierung im nächsten Abrufintervall fortzusetzen. Aus Gründen der Übersichtlichkeit verwendet dieses Beispiel einen hartcodierten Wert.
  6. Sie wird ausgeführt, bis ^die aktuelle UTC-Zeit den Ablaufzeitpunkt des Monitors überschreitet oder eine SMS-Benachrichtigung gesendet wird.

Mehrere Orchestratorinstanzen können gleichzeitig ausgeführt werden, indem die Orchestratorfunktion mehrmals aufgerufen wird. Der zu überwachende Standort und die Telefonnummer, an die eine SMS-Benachrichtigung gesendet werden soll, können angegeben werden. Beachten Sie schließlich, dass die Orchestratorfunktion während des Wartens auf den Timer nicht ausgeführt wird, daher werden Ihnen keine Gebühren dafür berechnet.

Aktivitätsfunktion „E3_GetIsClear“

Bei den Hilfsaktivitätsfunktionen handelt es sich, wie bei anderen Beispielen, um reguläre Funktionen, die die Triggerbindung activityTrigger verwenden. Die Funktion E3_GetIsClear ruft mithilfe der Weather Underground-API die aktuellen Wetterbedingungen ab und ermittelt, ob das Wetter schön ist.

[FunctionName("E3_GetIsClear")]
public static async Task<bool> GetIsClear([ActivityTrigger] Location location)
{
    var currentConditions = await WeatherUnderground.GetCurrentConditionsAsync(location);
    return currentConditions.Equals(WeatherCondition.Clear);
}

Aktivitätsfunktion „E3_SendGoodWeatherAlert“

Die Funktion E3_SendGoodWeatherAlert verwendet die Twilio-Bindung, um eine SMS zu senden, die den Endbenutzer darüber informiert, dass jetzt ein guter Zeitpunkt für einen Spaziergang ist.

    [FunctionName("E3_SendGoodWeatherAlert")]
    public static void SendGoodWeatherAlert(
        [ActivityTrigger] string phoneNumber,
        ILogger log,
        [TwilioSms(AccountSidSetting = "TwilioAccountSid", AuthTokenSetting = "TwilioAuthToken", From = "%TwilioPhoneNumber%")]
            out CreateMessageOptions message)
    {
        message = new CreateMessageOptions(new PhoneNumber(phoneNumber));
        message.Body = $"The weather's clear outside! Go take a walk!";
    }

internal class WeatherUnderground
{
    private static readonly HttpClient httpClient = new HttpClient();
    private static IReadOnlyDictionary<string, WeatherCondition> weatherMapping = new Dictionary<string, WeatherCondition>()
    {
        { "Clear", WeatherCondition.Clear },
        { "Overcast", WeatherCondition.Clear },
        { "Cloudy", WeatherCondition.Clear },
        { "Clouds", WeatherCondition.Clear },
        { "Drizzle", WeatherCondition.Precipitation },
        { "Hail", WeatherCondition.Precipitation },
        { "Ice", WeatherCondition.Precipitation },
        { "Mist", WeatherCondition.Precipitation },
        { "Precipitation", WeatherCondition.Precipitation },
        { "Rain", WeatherCondition.Precipitation },
        { "Showers", WeatherCondition.Precipitation },
        { "Snow", WeatherCondition.Precipitation },
        { "Spray", WeatherCondition.Precipitation },
        { "Squall", WeatherCondition.Precipitation },
        { "Thunderstorm", WeatherCondition.Precipitation },
    };

    internal static async Task<WeatherCondition> GetCurrentConditionsAsync(Location location)
    {
        var apiKey = Environment.GetEnvironmentVariable("WeatherUndergroundApiKey");
        if (string.IsNullOrEmpty(apiKey))
        {
            throw new InvalidOperationException("The WeatherUndergroundApiKey environment variable was not set.");
        }

        var callString = string.Format("http://api.wunderground.com/api/{0}/conditions/q/{1}/{2}.json", apiKey, location.State, location.City);
        var response = await httpClient.GetAsync(callString);
        var conditions = await response.Content.ReadAsAsync<JObject>();

        JToken currentObservation;
        if (!conditions.TryGetValue("current_observation", out currentObservation))
        {
            JToken error = conditions.SelectToken("response.error");

            if (error != null)
            {
                throw new InvalidOperationException($"API returned an error: {error}.");
            }
            else
            {
                throw new ArgumentException("Could not find weather for this location. Try being more specific.");
            }
        }

        return MapToWeatherCondition((string)(currentObservation as JObject).GetValue("weather"));
    }

    private static WeatherCondition MapToWeatherCondition(string weather)
    {
        foreach (var pair in weatherMapping)
        {
            if (weather.Contains(pair.Key))
            {
                return pair.Value;
            }
        }

        return WeatherCondition.Other;
    }
}

Hinweis

Sie müssen das NuGet-Paket Microsoft.Azure.WebJobs.Extensions.Twilio installieren, um den Beispielcode auszuführen.

Ausführen des Beispiels

Wenn Sie die über HTTP ausgelösten Funktionen verwenden, die im Beispiel enthalten sind, können Sie mit der Orchestrierung beginnen, indem Sie folgende HTTP POST-Anforderung senden:

POST https://{host}/orchestrators/E3_Monitor
Content-Length: 77
Content-Type: application/json

{ "location": { "city": "Redmond", "state": "WA" }, "phone": "+1425XXXXXXX" }
HTTP/1.1 202 Accepted
Content-Type: application/json; charset=utf-8
Location: https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635?taskHub=SampleHubVS&connection=Storage&code={SystemKey}
RetryAfter: 10

{"id": "f6893f25acf64df2ab53a35c09d52635", "statusQueryGetUri": "https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635?taskHub=SampleHubVS&connection=Storage&code={systemKey}", "sendEventPostUri": "https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635/raiseEvent/{eventName}?taskHub=SampleHubVS&connection=Storage&code={systemKey}", "terminatePostUri": "https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635/terminate?reason={text}&taskHub=SampleHubVS&connection=Storage&code={systemKey}"}

Die Instanz E3_Monitor wird gestartet und ruft die aktuellen Wetterbedingungen für den angeforderten Standort ab. Wenn das Wetter schön ist, ruft die Instanz eine Aktivitätsfunktion auf, die eine Benachrichtigung sendet. Andernfalls legt sie einen Timer fest. Wenn der Timer abläuft, wird die Orchestrierung fortgesetzt.

Sie können sich die Aktivität der Orchestrierung in den Funktionsprotokollen im Azure Functions-Portal ansehen.

2018-03-01T01:14:41.649 Function started (Id=2d5fcadf-275b-4226-a174-f9f943c90cd1)
2018-03-01T01:14:42.741 Started orchestration with ID = '1608200bb2ce4b7face5fc3b8e674f2e'.
2018-03-01T01:14:42.780 Function completed (Success, Id=2d5fcadf-275b-4226-a174-f9f943c90cd1, Duration=1111ms)
2018-03-01T01:14:52.765 Function started (Id=b1b7eb4a-96d3-4f11-a0ff-893e08dd4cfb)
2018-03-01T01:14:52.890 Received monitor request. Location: Redmond, WA. Phone: +1425XXXXXXX.
2018-03-01T01:14:52.895 Instantiating monitor for Redmond, WA. Expires: 3/1/2018 7:14:52 AM.
2018-03-01T01:14:52.909 Checking current weather conditions for Redmond, WA at 3/1/2018 1:14:52 AM.
2018-03-01T01:14:52.954 Function completed (Success, Id=b1b7eb4a-96d3-4f11-a0ff-893e08dd4cfb, Duration=189ms)
2018-03-01T01:14:53.226 Function started (Id=80a4cb26-c4be-46ba-85c8-ea0c6d07d859)
2018-03-01T01:14:53.808 Function completed (Success, Id=80a4cb26-c4be-46ba-85c8-ea0c6d07d859, Duration=582ms)
2018-03-01T01:14:53.967 Function started (Id=561d0c78-ee6e-46cb-b6db-39ef639c9a2c)
2018-03-01T01:14:53.996 Next check for Redmond, WA at 3/1/2018 1:44:53 AM.
2018-03-01T01:14:54.030 Function completed (Success, Id=561d0c78-ee6e-46cb-b6db-39ef639c9a2c, Duration=62ms)

Die Orchestrierung wird abgeschlossen, sobald das Zeitlimit erreicht ist oder schönes Wetter erkannt wird. Sie können auch die terminate-API in einer anderen Funktion verwenden oder den HTTP POST-Webhook terminatePostUri aufrufen, auf den oben in der 202-Antwort verwiesen wird. Ersetzen Sie {text} durch den Grund für die vorzeitige Beendigung, um den Webhook zu verwenden. Die HTTP POST-URL sieht in etwa wie folgt aus:

POST https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635/terminate?reason=Because&taskHub=SampleHubVS&connection=Storage&code={systemKey}

Nächste Schritte

Dieses Beispiel veranschaulichte, wie Sie Durable Functions verwenden, um den Status einer externen Quelle mithilfe von permanenten Timern und Bedingungslogik zu überwachen. Das nächste Beispiel zeigt, wie externe Ereignisse und permanente Timer verwendet werden, um Benutzerinteraktionen zu verarbeiten.