Delen via


Scenario bewaken in Durable Functions - Weather watcher-voorbeeld

Het monitorpatroon verwijst naar een flexibel terugkerend proces in een werkstroom, bijvoorbeeld polling tot aan bepaalde voorwaarden wordt voldaan. In dit artikel wordt een voorbeeld uitgelegd waarin Durable Functions wordt gebruikt om bewaking te implementeren.

Vereisten

Overzicht van scenario

In dit voorbeeld wordt de huidige weersomstandigheden van een locatie bewaakt en wordt een gebruiker per sms gewaarschuwd wanneer de lucht leeg is. U kunt een normale door timer geactiveerde functie gebruiken om het weer te controleren en waarschuwingen te verzenden. Een probleem met deze benadering is echter levensduurbeheer. Als er slechts één waarschuwing moet worden verzonden, moet de monitor zichzelf uitschakelen nadat het heldere weer is gedetecteerd. Het bewakingspatroon kan een eigen uitvoering beëindigen, onder andere voordelen:

  • Monitors worden uitgevoerd op intervallen, niet planningen: een timertrigger wordt elk uur uitgevoerd ; een monitor wacht één uur tussen acties. De acties van een monitor overlappen niet tenzij opgegeven, wat belangrijk kan zijn voor langlopende taken.
  • Monitors kunnen dynamische intervallen hebben: de wachttijd kan worden gewijzigd op basis van een bepaalde voorwaarde.
  • Monitors kunnen worden beëindigd wanneer aan bepaalde voorwaarden wordt voldaan of worden beëindigd door een ander proces.
  • Monitors kunnen parameters aannemen. In het voorbeeld ziet u hoe hetzelfde weerbewakingsproces kan worden toegepast op elke aangevraagde locatie en elk telefoonnummer.
  • Monitors zijn schaalbaar. Omdat elke monitor een indelingsexemplaar is, kunnen meerdere monitors worden gemaakt zonder nieuwe functies te hoeven maken of meer code te definiëren.
  • Monitors kunnen eenvoudig worden geïntegreerd in grotere werkstromen. Een monitor kan één sectie van een complexere indelingsfunctie of een subindeling zijn.

Configuratie

Twilio-integratie configureren

Dit voorbeeld omvat het gebruik van de Twilio-service om sms-berichten naar een mobiele telefoon te verzenden. Azure Functions biedt al ondersteuning voor Twilio via de Twilio-binding en het voorbeeld gebruikt die functie.

Het eerste wat u nodig hebt, is een Twilio-account. U kunt er een gratis maken op https://www.twilio.com/try-twilio. Nadat u een account hebt, voegt u de volgende drie app-instellingen toe aan uw functie-app.

Naam van de app-instelling Beschrijving van waarde
TwilioAccountSid De SID voor uw Twilio-account
TwilioAuthToken Het verificatietoken voor uw Twilio-account
TwilioPhoneNumber Het telefoonnummer dat is gekoppeld aan uw Twilio-account. Dit wordt gebruikt om sms-berichten te verzenden.

Weather Underground-integratie configureren

Dit voorbeeld omvat het gebruik van de Weer underground-API om de huidige weersomstandigheden voor een locatie te controleren.

Het eerste wat je nodig hebt, is een Weer ondergronds account. U kunt er gratis een maken op https://www.wunderground.com/signup. Zodra u een account hebt, moet u een API-sleutel verkrijgen. U kunt dit doen door naar Key Settings te https://www.wunderground.com/weather/apigaan en vervolgens Sleutelinstellingen te selecteren. Het Stratus Developer-plan is gratis en voldoende om dit voorbeeld uit te voeren.

Zodra u een API-sleutel hebt, voegt u de volgende app-instelling toe aan uw functie-app.

Naam van de app-instelling Beschrijving van waarde
WeatherUndergroundApiKey Uw Weather Underground API-sleutel.

De functies

In dit artikel worden de volgende functies in de voorbeeld-app uitgelegd:

  • E3_Monitor: Een orchestratorfunctie die periodiek wordt aangeroepen E3_GetIsClear . Het roept aan of E3_GetIsClear retourneert E3_SendGoodWeatherAlert waar.
  • E3_GetIsClear: Een activiteitsfunctie die de huidige weersomstandigheden voor een locatie controleert.
  • E3_SendGoodWeatherAlert: Een activiteitsfunctie die een SMS-bericht verzendt via Twilio.

E3_Monitor orchestratorfunctie

[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.");
    }
}

De orchestrator vereist een locatie om te controleren en een telefoonnummer om een bericht te verzenden wanneer het weer op de locatie duidelijk wordt. Deze gegevens worden doorgegeven aan de orchestrator als een sterk getypt MonitorRequest object.

Met deze orchestratorfunctie worden de volgende acties uitgevoerd:

  1. Hiermee haalt u de MonitorRequest op die bestaat uit de locatie die moet worden bewaakt en het telefoonnummer waarnaar een sms-melding wordt verzonden.
  2. Bepaalt de verlooptijd van de monitor. In het voorbeeld wordt een in code vastgelegde waarde gebruikt voor de beknoptheid.
  3. Roept E3_GetIsClear aan om te bepalen of er duidelijke luchten zijn op de aangevraagde locatie.
  4. Als het weer duidelijk is, worden oproepen E3_SendGoodWeatherAlert om een sms-melding naar het aangevraagde telefoonnummer te verzenden.
  5. Hiermee maakt u een duurzame timer om de indeling te hervatten op het volgende polling-interval. In het voorbeeld wordt een in code vastgelegde waarde gebruikt voor de beknoptheid.
  6. Blijft actief totdat de huidige UTC-tijd de verlooptijd van de monitor doorgeeft of een sms-waarschuwing wordt verzonden.

Meerdere orchestrator-exemplaren kunnen tegelijkertijd worden uitgevoerd door de orchestratorfunctie meerdere keren aan te roepen. De locatie die moet worden bewaakt en het telefoonnummer waarop een sms-waarschuwing moet worden verzonden, kan worden opgegeven. Houd er ten slotte rekening mee dat de orchestratorfunctie niet* wordt uitgevoerd tijdens het wachten op de timer, zodat er geen kosten in rekening worden gebracht.

E3_GetIsClear activiteitsfunctie

Net als bij andere voorbeelden zijn de helperactiviteitsfuncties reguliere functies die gebruikmaken van de activityTrigger triggerbinding. De E3_GetIsClear functie haalt de huidige weersomstandigheden op met behulp van de Weer ondergrondse API en bepaalt of de lucht helder is.

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

E3_SendGoodWeatherAlert activiteitsfunctie

De functie E3_SendGoodWeatherAlert maakt gebruik van de Twilio-binding om een sms-bericht te verzenden waarin de eindgebruiker wordt geïnformeerd dat het een goed moment is voor een wandeling.

    [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;
    }
}

Notitie

U moet het Microsoft.Azure.WebJobs.Extensions.Twilio Nuget-pakket installeren om de voorbeeldcode uit te voeren.

De voorbeeldtoepassing uitvoeren

Met behulp van de door HTTP geactiveerde functies in het voorbeeld kunt u de indeling starten door de volgende HTTP POST-aanvraag te verzenden:

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}"}

Het E3_Monitor exemplaar wordt gestart en voert een query uit op de huidige weersomstandigheden voor de aangevraagde locatie. Als het weer helder is, wordt een activiteitsfunctie aangeroepen om een waarschuwing te verzenden; anders wordt er een timer ingesteld. Wanneer de timer verloopt, wordt de indeling hervat.

U kunt de activiteit van de indeling bekijken door de functielogboeken in de Azure Functions-portal te bekijken.

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)

De indeling wordt voltooid zodra de time-out is bereikt of de heldere hemel wordt gedetecteerd. U kunt de terminate API ook in een andere functie gebruiken of de terminatePostUri HTTP POST-webhook aanroepen waarnaar wordt verwezen in het voorgaande 202-antwoord. Als u de webhook wilt gebruiken, vervangt u deze door {text} de reden voor vroegtijdige beëindiging. De HTTP POST-URL ziet er ongeveer als volgt uit:

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

Volgende stappen

In dit voorbeeld ziet u hoe u Durable Functions gebruikt om de status van een externe bron te bewaken met behulp van duurzame timers en voorwaardelijke logica. In het volgende voorbeeld ziet u hoe u externe gebeurtenissen en duurzame timers gebruikt om menselijke interactie af te handelen.