Condividi tramite


Scenario di monitoraggio in Funzioni durevoli - Esempio di watcher per il meteo

Il modello di monitoraggio fa riferimento a un processo ricorrente flessibile in un flusso di lavoro, ad esempio il polling finché vengono soddisfatte determinate condizioni. Questo articolo illustra un esempio che usa Funzioni durevoli per implementare il monitoraggio.

Prerequisiti

Panoramica dello scenario

Questo esempio monitora le condizioni meteo correnti di una località e avvisa un utente tramite SMS quando il tempo è sereno. Per controllare il meteo e inviare gli avvisi, è possibile usare una normale funzione attivata tramite timer. Un problema di questo approccio è però la gestione della durata. Se deve essere inviato un solo avviso, il monitoraggio deve disabilitarsi dopo che sono state rilevate condizioni di tempo sereno. Il modello di monitoraggio può terminare la propria esecuzione, tra gli altri vantaggi:

  • I monitoraggi vengono eseguiti a intervalli, non in base a pianificazioni: un trigger timer viene eseguito ogni ora. Un monitoraggio attende un'ora tra un'azione e l'altra. Se non diversamente specificato, le azioni di monitoraggio non si sovrappongono, cosa che può essere importante per le attività con esecuzione prolungata.
  • I monitoraggi possono avere intervalli dinamici: il tempo di attesa può variare in base ad alcune condizioni.
  • I monitoraggi possono terminare quando viene soddisfatta una condizione o essere terminati da un altro processo.
  • Monitoraggi possono accettare parametri. L'esempio illustra come lo stesso processo di monitoraggio del meteo possa essere applicato a qualsiasi località richiesta e numero di telefono.
  • Monitoraggi sono scalabili. Poiché ogni monitoraggio è un'istanza di orchestrazione, si possono creare più monitoraggi senza dover creare nuove funzioni o definire altro codice.
  • I monitoraggi si integrano facilmente in flussi di lavoro più grandi. Un monitoraggio può essere una sezione di una funzione di orchestrazione più complessa o un'orchestrazione secondaria.

Impostazione

Configurazione dell'integrazione di Twilio

Questo esempio prevede l'uso del servizio Twilio per inviare messaggi SMS al telefono cellulare. Funzioni di Azure supporta già Twilio tramite l'associazione a Twilio e l'esempio usa tale funzionalità.

È necessario per prima cosa disporre di un account Twilio. È possibile crearne uno gratuitamente all'indirizzo https://www.twilio.com/try-twilio. Dopo aver creato un account, aggiungere le tre impostazioni all'app per le funzioni.

Nome impostazione app Descrizione valore
TwilioAccountSid SID dell'account Twilio
TwilioAuthToken Token di autenticazione per l'account Twilio
TwilioPhoneNumber Numero di telefono associato all'account Twilio usato per inviare messaggi SMS.

Configurazione dell'integrazione di Weather Underground

Questo esempio prevede l'uso dell'API Weather Underground per controllare le condizioni meteo correnti di una località.

Prima di tutto, è necessario un account Weather Underground. È possibile crearne uno gratuitamente all'indirizzo https://www.wunderground.com/signup. Dopo avere creato l'account, è necessario acquisire una chiave API. A questo scopo, visitare https://www.wunderground.com/weather/api, quindi selezionare Key Settings (Impostazioni chiave). Il piano gratuito Stratus Developer è sufficiente per eseguire questo esempio.

Dopo avere acquisito la chiave API, aggiungere l'impostazione app seguente all'app per le funzioni.

Nome impostazione app Descrizione valore
WeatherUndergroundApiKey Chiave API Weather Underground.

Funzioni

Questo articolo descrive le funzioni seguenti nell'app di esempio:

  • E3_Monitor: funzione dell’agente di orchestrazione che chiama E3_GetIsClear periodicamente. Chiama E3_SendGoodWeatherAlert se E3_GetIsClear restituisce true.
  • E3_GetIsClear: funzione dell’attività che controlla le condizioni meteo correnti di una località.
  • E3_SendGoodWeatherAlert: funzione dell'attività che invia un SMS tramite Twilio.

Funzione dell’agente di orchestrazione 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.");
    }
}

L'agente di orchestrazione richiede una località per il monitoraggio e un numero di telefono a cui inviare un messaggio quando le condizioni meteo diventano serene nella località. Questi dati vengono passati all'agente di orchestrazione come oggetto MonitorRequest fortemente tipizzato.

Le azioni di questa funzione dell'agente di orchestrazione sono le seguenti:

  1. Ottiene MonitorRequest costituita dalla località da monitorare e dal numero di telefono al quale viene inviato un SMS di notifica.
  2. Determina la scadenza del monitoraggio. L'esempio usa un valore hardcoded per ragioni di brevità.
  3. Chiama E3_GetIsClear per determinare se nella località richiesta il tempo è sereno.
  4. Se il tempo è sereno, chiama E3_SendGoodWeatherAlert per inviare un SMS di notifica al numero di telefono richiesto.
  5. Crea un timer durevole per riprendere l'orchestrazione all'intervallo di polling successivo. L'esempio usa un valore hardcoded per ragioni di brevità.
  6. Continua l'esecuzione fino a che l’ora UTC corrente supera l’ora di scadenza del monitoraggio o viene inviato un SMS di avviso.

È possibile eseguire più istanze dell'agente di orchestrazione simultaneamente chiamando la funzione dell'agente di orchestrazione più volte. Si possono specificare la località da monitorare e il numero di telefono a cui inviare un SMS di avviso. Infine, tenere presente che poiché la funzione dell'agente di orchestrazione non è in esecuzione durante l'attesa del timer non è previsto alcun addebito.

Funzione dell’attività E3_GetIsClear

In modo analogo agli altri esempi, le funzioni di attività helper sono normali funzioni che usano l'associazione di trigger activityTrigger. La funzione E3_GetIsClear ottiene le condizioni meteo correnti usando l'API Weather Underground e determina se il tempo è sereno.

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

Funzione dell’attività E3_SendGoodWeatherAlert

La funzione E3_SendGoodWeatherAlert usa l'associazione di Twilio per inviare un SMS che notifica all'utente finale che il tempo è adatto per una passeggiata.

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

Nota

È necessario installare il pacchetto NuGet Microsoft.Azure.WebJobs.Extensions.Twilio per eseguire il codice di esempio.

Eseguire l'esempio

Con le funzioni attivate da HTTP incluse nell'esempio, è possibile avviare l'orchestrazione inviando la richiesta HTTP POST seguente:

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

L'istanza E3_Monitor viene avviata ed esegue una query delle condizioni meteo correnti per la località richiesta. Se il tempo è sereno, chiama una funzione dell'attività per inviare un avviso. In caso contrario, imposta un timer. Quando il timer scade, l'orchestrazione viene riavviata.

È possibile visualizzare l'attività dell'orchestrazione esaminando i log della funzione nel portale Funzioni di Azure.

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)

L'orchestrazione termina quando viene raggiunto il timeout o viene rilevato tempo sereno. È anche possibile usare l'API terminate all'interno di un'altra funzione o richiamare il webhook HTTP POST terminatePostUri a cui si fa riferimento nella risposta 202 precedente. Per usare il webhook, sostituire {text} con il motivo della terminazione anticipata. L'URL HTTP POST è simile al seguente:

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

Passaggi successivi

Questo esempio ha illustrato come usare Durable Functions per monitorare lo stato di un'origine esterna con timer durevoli e la logica condizionale. Nell'esempio successivo viene illustrato come usare eventi esterni e timer durevoli per gestire l'interazione umana.