Monitorowanie scenariusza w Durable Functions — przykład usługi Weather Watcher

Wzorzec monitora odnosi się do elastycznego cyklicznego procesu w przepływie pracy — na przykład sondowania do momentu spełnienia określonych warunków. W tym artykule wyjaśniono przykład, który używa Durable Functions do implementowania monitorowania.

Wymagania wstępne

Omówienie scenariusza

Ten przykład monitoruje bieżące warunki pogodowe lokalizacji i ostrzega użytkownika za pomocą wiadomości SMS, gdy niebo jest jasne. Możesz użyć funkcji wyzwalanej przez czasomierz regularny, aby sprawdzić pogodę i wysłać alerty. Jednak jednym z problemów z tym podejściem jest zarządzanie okresem istnienia. Jeśli powinien zostać wysłany tylko jeden alert, monitor musi wyłączyć się po wykryciu jasnej pogody. Wzorzec monitorowania może zakończyć własne wykonywanie, między innymi korzyści:

  • Monitory są uruchamiane w interwałach, a nie harmonogramy: wyzwalacz czasomierza jest uruchamiany co godzinę; monitor czeka godzinę między akcjami. Akcje monitora nie nakładają się, chyba że określono, co może być ważne w przypadku długotrwałych zadań.
  • Monitory mogą mieć interwały dynamiczne: czas oczekiwania może ulec zmianie na podstawie pewnego warunku.
  • Monitory mogą zakończyć się, gdy zostanie spełniony jakiś warunek lub zostanie zakończony przez inny proces.
  • Monitory mogą przyjmować parametry. W przykładzie pokazano, jak można zastosować ten sam proces monitorowania pogody do dowolnej żądanej lokalizacji i numeru telefonu.
  • Monitory są skalowalne. Ponieważ każdy monitor jest wystąpieniem orkiestracji, można utworzyć wiele monitorów bez konieczności tworzenia nowych funkcji lub definiowania większej liczby kodu.
  • Monitory można łatwo zintegrować z większymi przepływami pracy. Monitor może być jedną z sekcji bardziej złożonej funkcji orkiestracji lub podarancji.

Konfigurowanie

Konfigurowanie integracji usługi Twilio

Ten przykład obejmuje używanie usługi Twilio do wysyłania wiadomości SMS na telefon komórkowy. Azure Functions ma już obsługę usługi Twilio za pośrednictwem powiązania usługi Twilio, a w przykładzie użyto tej funkcji.

Pierwszą rzeczą, której potrzebujesz, jest konto usługi Twilio. Możesz utworzyć jedną bezpłatnie na stronie https://www.twilio.com/try-twilio. Po utworzeniu konta dodaj następujące trzy ustawienia aplikacji do aplikacji funkcji.

Nazwa ustawienia aplikacji Opis wartości
TwilioAccountSid Identyfikator SID konta usługi Twilio
TwilioAuthToken Token uwierzytelniania dla konta usługi Twilio
TwilioPhoneNumber Numer telefonu skojarzony z kontem usługi Twilio. Służy do wysyłania wiadomości SMS.

Konfigurowanie integracji z platformą Weather Underground

Ten przykład obejmuje użycie interfejsu API Weather Underground w celu sprawdzenia bieżących warunków pogodowych dla lokalizacji.

Pierwszą rzeczą, której potrzebujesz, jest konto Weather Underground. Możesz utworzyć go bezpłatnie na stronie https://www.wunderground.com/signup. Po utworzeniu konta musisz uzyskać klucz interfejsu API. Możesz to zrobić, odwiedzając stronę https://www.wunderground.com/weather/api, a następnie wybierając pozycję Ustawienia klucza. Plan Stratus Developer jest bezpłatny i wystarczający do uruchomienia tego przykładu.

Po utworzeniu klucza interfejsu API dodaj następujące ustawienie aplikacji do aplikacji funkcji.

Nazwa ustawienia aplikacji Opis wartości
WeatherUndergroundApiKey Klucz interfejsu API Weather Underground.

Funkcje

W tym artykule opisano następujące funkcje w przykładowej aplikacji:

  • E3_Monitor: funkcja orkiestratora , która okresowo wywołuje E3_GetIsClear . Wywołuje E3_SendGoodWeatherAlert metodę if E3_GetIsClear zwraca wartość true.
  • E3_GetIsClear: funkcja działania , która sprawdza bieżące warunki pogodowe dla lokalizacji.
  • E3_SendGoodWeatherAlert: funkcja działania, która wysyła wiadomość SMS za pośrednictwem usługi Twilio.

E3_Monitor funkcja orkiestratora

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

Orkiestrator wymaga lokalizacji do monitorowania i numeru telefonu, aby wysłać wiadomość do, kiedy to stanie się jasne w lokalizacji. Te dane są przekazywane do orkiestratora jako silnie typizowanego MonitorRequest obiektu.

Ta funkcja orkiestratora wykonuje następujące akcje:

  1. Pobiera element MonitorRequest składający się z lokalizacji do monitorowania i numeru telefonu , do którego wyśle powiadomienie SMS.
  2. Określa czas wygaśnięcia monitora. W przykładzie użyto trwale zakodowanej wartości dla zwięzłości.
  3. Wzywa E3_GetIsClear , aby ustalić, czy w żądanym miejscu znajdują się jasne niebo.
  4. Jeśli pogoda jest jasna, dzwoni E3_SendGoodWeatherAlert , aby wysłać powiadomienie SMS do żądanego numeru telefonu.
  5. Tworzy trwały czasomierz, aby wznowić aranżację w następnym interwale sondowania. W przykładzie użyto trwale zakodowanej wartości dla zwięzłości.
  6. Trwa działanie do czasu wygaśnięcia bieżącego czasu UTC lub wysłania alertu SMS.

Wiele wystąpień orkiestratora może działać jednocześnie, wywołując funkcję orkiestratora wiele razy. Można określić lokalizację monitorowania oraz numer telefonu do wysłania alertu SMS. Na koniec należy pamiętać, że funkcja orkiestratora nie jest uruchomiona podczas oczekiwania na czasomierz, więc nie zostanie naliczona opłata.

E3_GetIsClear, funkcja działania

Podobnie jak w przypadku innych przykładów, funkcje działania pomocnika są zwykłymi funkcjami korzystającymi z activityTrigger powiązania wyzwalacza. Funkcja E3_GetIsClear pobiera bieżące warunki pogodowe przy użyciu interfejsu API Weather Underground i określa, czy niebo jest jasne.

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

funkcja działania E3_SendGoodWeatherAlert

Funkcja E3_SendGoodWeatherAlert używa powiązania usługi Twilio do wysyłania wiadomości SMS z powiadomieniem użytkownika końcowego, że jest to dobry czas na spacer.

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

Uwaga

Aby uruchomić przykładowy kod, musisz zainstalować Microsoft.Azure.WebJobs.Extensions.Twilio pakiet Nuget.

Uruchamianie aplikacji przykładowej

Korzystając z funkcji wyzwalanych przez protokół HTTP zawartych w przykładzie, można uruchomić orkiestrację, wysyłając następujące żądanie HTTP POST:

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

Wystąpienie E3_Monitor uruchamia się i wysyła zapytanie do bieżących warunków pogodowych dla żądanej lokalizacji. Jeśli pogoda jest jasna, wywołuje funkcję działania, aby wysłać alert; w przeciwnym razie ustawia czasomierz. Po wygaśnięciu czasomierza orkiestracja zostanie wznowione.

Działanie orkiestracji można zobaczyć, przeglądając dzienniki funkcji w portalu Azure Functions.

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)

Aranżacja kończy się po osiągnięciu limitu czasu lub wykryciu jasnego nieba. Możesz również użyć interfejsu terminate API wewnątrz innej funkcji lub wywołać element webhook HTTP POST terminatePostUri , do którego odwołuje się powyższy element webhook 202. Aby użyć elementu webhook, zastąp {text} element przyczyną wczesnego zakończenia. Adres URL POST protokołu HTTP będzie wyglądać mniej więcej w następujący sposób:

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

Następne kroki

W tym przykładzie pokazano, jak używać Durable Functions do monitorowania stanu źródła zewnętrznego przy użyciu trwałych czasomierzy i logiki warunkowej. W następnym przykładzie pokazano, jak używać zdarzeń zewnętrznych i trwałych czasomierzy do obsługi interakcji z ludźmi.