Cenário de monitorização no Durable Functions – Exemplo do Observador meteorológico
O padrão de monitorização refere-se a um processo recorrente flexível num fluxo de trabalho, por exemplo, consulta até que determinadas condições sejam cumpridas. Este artigo explica um exemplo que utiliza Durable Functions para implementar a monitorização.
Pré-requisitos
Scenario overview (Descrição geral do cenário)
Este exemplo monitoriza as condições meteorológicas atuais de uma localização e alerta um utilizador por SMS quando o céu estiver limpo. Pode utilizar uma função normal acionada por temporizador para verificar a meteorologia e enviar alertas. No entanto, um problema com esta abordagem é a gestão de duração. Se apenas um alerta deve ser enviado, o monitor tem de se desativar após a deteção de uma meteorologia clara. O padrão de monitorização pode terminar a sua própria execução, entre outros benefícios:
- Os monitores são executados em intervalos e não em agendas: um acionador de temporizador é executado a cada hora; um monitor aguarda uma hora entre ações. As ações de um monitor não se sobrepõem a menos que seja especificado, o que pode ser importante para tarefas de execução prolongada.
- Os monitores podem ter intervalos dinâmicos: o tempo de espera pode mudar com base em alguma condição.
- Os monitores podem terminar quando alguma condição é cumprida ou terminada por outro processo.
- Os monitores podem utilizar parâmetros. O exemplo mostra como o mesmo processo de monitorização meteorológica pode ser aplicado a qualquer localização e número de telefone pedidos.
- Os monitores são dimensionáveis. Uma vez que cada monitor é uma instância de orquestração, podem ser criados vários monitores sem ter de criar novas funções ou definir mais código.
- Os monitores integram-se facilmente em fluxos de trabalho maiores. Um monitor pode ser uma secção de uma função de orquestração mais complexa ou uma sub-orquestração.
Configuração
Configurar a integração do Twilio
Este exemplo envolve a utilização do serviço Twilio para enviar mensagens SMS para um telemóvel. Funções do Azure já tem suporte para o Twilio através do enlace do Twilio e o exemplo utiliza essa funcionalidade.
A primeira coisa de que precisa é de uma conta do Twilio. Pode criar uma gratuitamente em https://www.twilio.com/try-twilio. Assim que tiver uma conta, adicione as seguintes três definições de aplicação à sua aplicação de funções.
Nome da definição da aplicação | Descrição do valor |
---|---|
TwilioAccountSid | O SID da sua conta do Twilio |
TwilioAuthToken | O token de Autenticação para a sua conta do Twilio |
TwilioPhoneNumber | O número de telefone associado à sua conta do Twilio. Isto é utilizado para enviar mensagens SMS. |
Configurar a integração do Weather Underground
Este exemplo envolve a utilização da API Weather Underground para verificar as condições meteorológicas atuais para uma localização.
A primeira coisa que precisa é de uma conta do Weather Underground. Pode criar uma gratuitamente em https://www.wunderground.com/signup. Assim que tiver uma conta, terá de adquirir uma chave de API. Pode fazê-lo visitando https://www.wunderground.com/weather/apie, em seguida, selecionando Definições de Teclas de Atalho. O plano Stratus Developer é gratuito e suficiente para executar este exemplo.
Assim que tiver uma chave de API, adicione a seguinte definição de aplicação à sua aplicação de funções.
Nome da definição da aplicação | Descrição do valor |
---|---|
WeatherUndergroundApiKey | A chave da API Weather Underground. |
As funções
Este artigo explica as seguintes funções na aplicação de exemplo:
E3_Monitor
: uma função de orquestrador que chamaE3_GetIsClear
periodicamente. ChamaE3_SendGoodWeatherAlert
seE3_GetIsClear
devolver verdadeiro.E3_GetIsClear
: uma função de atividade que verifica as condições meteorológicas atuais para uma localização.E3_SendGoodWeatherAlert
: uma função de atividade que envia uma mensagem SMS através do Twilio.
E3_Monitor função orchestrator
[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.");
}
}
O orquestrador necessita de uma localização para monitorizar e um número de telefone para enviar uma mensagem para quando o fica claro na localização. Estes dados são transmitidos ao orquestrador como um objeto fortemente escrito MonitorRequest
.
Esta função de orquestrador executa as seguintes ações:
- Obtém o MonitorRequest que consiste na localização a monitorizar e o número de telefone para o qual enviará uma notificação por SMS.
- Determina a hora de expiração do monitor. O exemplo utiliza um valor hard-coded para a brevidade.
- Chama E3_GetIsClear para determinar se há céu limpo no local pedido.
- Se a meteorologia estiver desmarcada, as chamadas E3_SendGoodWeatherAlert para enviar uma notificação por SMS para o número de telefone pedido.
- Cria um temporizador durável para retomar a orquestração no próximo intervalo de consulta. O exemplo utiliza um valor hard-coded para a brevidade.
- Continua em execução até que a hora UTC atual passe a hora de expiração do monitor ou seja enviado um alerta de SMS.
Várias instâncias do orquestrador podem ser executadas em simultâneo ao chamar a função orchestrator várias vezes. Pode especificar a localização a monitorizar e o número de telefone para o qual enviar um alerta SMS. Por fim, tenha em atenção que a função orchestrator não está em execução enquanto aguarda o temporizador, pelo que não lhe será cobrado.
função de atividade E3_GetIsClear
Tal como acontece com outros exemplos, as funções de atividade auxiliar são funções regulares que utilizam o enlace do acionador activityTrigger
. A função E3_GetIsClear obtém as condições meteorológicas atuais com a API Weather Underground e determina se o céu está limpo.
[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 função de atividade
A função E3_SendGoodWeatherAlert utiliza o enlace do Twilio para enviar uma mensagem SMS a notificar o utilizador final de que é uma boa altura para uma caminhada.
[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
Terá de instalar o Microsoft.Azure.WebJobs.Extensions.Twilio
pacote Nuget para executar o código de exemplo.
Executar o exemplo
Ao utilizar as funções acionadas por HTTP incluídas no exemplo, pode iniciar a orquestração ao enviar o seguinte pedido 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}"}
A instância E3_Monitor é iniciada e consulta as condições meteorológicas atuais para a localização pedida. Se a meteorologia estiver clara, chama uma função de atividade para enviar um alerta; caso contrário, define um temporizador. Quando o temporizador expirar, a orquestração será retomada.
Pode ver a atividade da orquestração ao observar os registos de funções no portal de Funções do 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)
A orquestração é concluída assim que o tempo limite for atingido ou os céus limpos forem detetados. Também pode utilizar a terminate
API dentro de outra função ou invocar o webhook POST HTTP doPostUri referenciado na resposta 202 acima. Para utilizar o webhook, substitua {text}
pelo motivo da cessação antecipada. O URL HTTP POST terá o seguinte aspeto:
POST https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635/terminate?reason=Because&taskHub=SampleHubVS&connection=Storage&code={systemKey}
Passos seguintes
Este exemplo demonstrou como utilizar Durable Functions para monitorizar o estado de uma origem externa com temporizadores duráveis e lógica condicional. O exemplo seguinte mostra como utilizar eventos externos e temporizadores duráveis para lidar com a interação humana.