Interação humana em funções duráveis - Exemplo de verificação por telefone
Este exemplo demonstra como criar uma orquestração de funções duráveis que envolve a interação humana. Sempre que uma pessoa real está envolvida em um processo automatizado, o processo deve ser capaz de enviar notificações para a pessoa e receber respostas de forma assíncrona. Deve igualmente prever a possibilidade de a pessoa não estar disponível. (Esta última parte é onde os tempos limite se tornam importantes.)
Este exemplo implementa um sistema de verificação por telefone baseado em SMS. Esses tipos de fluxos são frequentemente usados ao verificar o número de telefone de um cliente ou para autenticação multifator (MFA). É um exemplo poderoso porque toda a implementação é feita usando algumas pequenas funções. Nenhum armazenamento de dados externo, como um banco de dados, é necessário.
Nota
A versão 4 do modelo de programação Node.js para o Azure Functions está disponível em geral. O novo modelo v4 foi projetado para ter uma experiência mais flexível e intuitiva para desenvolvedores de JavaScript e TypeScript. Saiba mais sobre as diferenças entre v3 e v4 no guia de migração.
Nos trechos de código a seguir, JavaScript (PM4) indica o modelo de programação V4, a nova experiência.
Pré-requisitos
Descrição geral do cenário
A verificação por telefone é usada para verificar se os usuários finais do seu aplicativo não são spammers e se eles são quem dizem ser. A autenticação multifator é um caso de uso comum para proteger contas de usuários contra hackers. O desafio de implementar sua própria verificação por telefone é que ela requer uma interação stateful com um ser humano. Um usuário final normalmente recebe algum código (por exemplo, um número de 4 dígitos) e deve responder em um período razoável de tempo.
O Azure Functions comum é sem monitoração de estado (assim como muitos outros pontos de extremidade de nuvem em outras plataformas), portanto, esses tipos de interações envolvem o gerenciamento explícito do estado externamente em um banco de dados ou em algum outro armazenamento persistente. Além disso, a interação deve ser dividida em múltiplas funções que podem ser coordenadas em conjunto. Por exemplo, você precisa de pelo menos uma função para decidir sobre um código, persisti-lo em algum lugar e enviá-lo para o telefone do usuário. Além disso, você precisa de pelo menos uma outra função para receber uma resposta do usuário e, de alguma forma, mapeá-la de volta para a chamada de função original, a fim de fazer a validação do código. Um tempo limite também é um aspeto importante para garantir a segurança. Pode tornar-se bastante complexo rapidamente.
A complexidade desse cenário é muito reduzida quando você usa funções duráveis. Como você verá neste exemplo, uma função orchestrator pode gerenciar a interação stateful facilmente e sem envolver nenhum armazenamento de dados externo. Como as funções do orquestrador são duráveis, esses fluxos interativos também são altamente confiáveis.
Configurando a integração do Twilio
Este exemplo envolve o uso do serviço Twilio para enviar mensagens SMS para um telefone celular. O Azure Functions já tem suporte para o Twilio por meio da associação do Twilio, e o exemplo usa esse recurso.
A primeira coisa que você precisa é de uma conta Twilio. Você pode criar um gratuitamente em https://www.twilio.com/try-twilio. Depois de ter uma conta, adicione as três configurações de aplicativo a seguir ao seu aplicativo de função.
Nome de definição de aplicação | Descrição do valor |
---|---|
TwilioAccountSid | O SID da sua conta Twilio |
TwilioAuthToken | O token de autenticação para sua conta Twilio |
TwilioPhoneNumber | O número de telefone associado à sua conta Twilio. Isso é usado para enviar mensagens SMS. |
As funções
Este artigo descreve as seguintes funções no aplicativo de exemplo:
E4_SmsPhoneVerification
: Uma função orquestradora que executa o processo de verificação do telefone, incluindo o gerenciamento de tempos limite e tentativas.E4_SendSmsChallenge
: Uma função de atividade que envia um código via mensagem de texto.
Nota
A HttpStart
função no aplicativo de exemplo e o início rápido atuam como cliente Orchestration que aciona a função orchestrator.
E4_SmsPhoneVerification função orquestradora
[FunctionName("E4_SmsPhoneVerification")]
public static async Task<bool> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
string phoneNumber = context.GetInput<string>();
if (string.IsNullOrEmpty(phoneNumber))
{
throw new ArgumentNullException(
nameof(phoneNumber),
"A phone number input is required.");
}
int challengeCode = await context.CallActivityAsync<int>(
"E4_SendSmsChallenge",
phoneNumber);
using (var timeoutCts = new CancellationTokenSource())
{
// The user has 90 seconds to respond with the code they received in the SMS message.
DateTime expiration = context.CurrentUtcDateTime.AddSeconds(90);
Task timeoutTask = context.CreateTimer(expiration, timeoutCts.Token);
bool authorized = false;
for (int retryCount = 0; retryCount <= 3; retryCount++)
{
Task<int> challengeResponseTask =
context.WaitForExternalEvent<int>("SmsChallengeResponse");
Task winner = await Task.WhenAny(challengeResponseTask, timeoutTask);
if (winner == challengeResponseTask)
{
// We got back a response! Compare it to the challenge code.
if (challengeResponseTask.Result == challengeCode)
{
authorized = true;
break;
}
}
else
{
// Timeout expired
break;
}
}
if (!timeoutTask.IsCompleted)
{
// All pending timers must be complete or canceled before the function exits.
timeoutCts.Cancel();
}
return authorized;
}
}
Nota
Pode não ser óbvio no início, mas este orquestrador não viola a restrição de orquestração determinista. É determinístico porque a CurrentUtcDateTime
propriedade é usada para calcular o tempo de expiração do temporizador e retorna o mesmo valor em cada repetição neste ponto do código do orquestrador. Esse comportamento é importante para garantir que o mesmo winner
resulte de cada chamada repetida para Task.WhenAny
.
Uma vez iniciada, esta função orquestradora faz o seguinte:
- Obtém um número de telefone para o qual enviará a notificação por SMS.
- Chama E4_SendSmsChallenge para enviar uma mensagem SMS para o usuário e retorna o código de desafio de 4 dígitos esperado.
- Cria um temporizador durável que dispara 90 segundos a partir da hora atual.
- Em paralelo com o temporizador, aguarda um evento SmsChallengeResponse do usuário.
O usuário recebe uma mensagem SMS com um código de quatro dígitos. Eles têm 90 segundos para enviar esse mesmo código de quatro dígitos de volta para a instância da função orchestrator para concluir o processo de verificação. Se eles enviarem o código errado, eles receberão mais três tentativas para acertar (dentro da mesma janela de 90 segundos).
Aviso
É importante cancelar os temporizadores se já não precisar que eles expirem, como no exemplo acima, quando uma resposta de desafio é aceite.
E4_SendSmsChallenge função de atividade
A função E4_SendSmsChallenge usa a ligação Twilio para enviar a mensagem SMS com o código de quatro dígitos para o usuário final.
[FunctionName("E4_SendSmsChallenge")]
public static int SendSmsChallenge(
[ActivityTrigger] string phoneNumber,
ILogger log,
[TwilioSms(AccountSidSetting = "TwilioAccountSid", AuthTokenSetting = "TwilioAuthToken", From = "%TwilioPhoneNumber%")]
out CreateMessageOptions message)
{
// Get a random number generator with a random seed (not time-based)
var rand = new Random(Guid.NewGuid().GetHashCode());
int challengeCode = rand.Next(10000);
log.LogInformation($"Sending verification code {challengeCode} to {phoneNumber}.");
message = new CreateMessageOptions(new PhoneNumber(phoneNumber));
message.Body = $"Your verification code is {challengeCode:0000}";
return challengeCode;
}
Nota
Você deve primeiro instalar o Microsoft.Azure.WebJobs.Extensions.Twilio
pacote Nuget para Functions para executar o código de exemplo. Não instale também o pacote nuget principal do Twilio porque isso pode causar problemas de versionamento que resultam em erros de compilação.
Executar o exemplo
Usando as funções acionadas por HTTP incluídas no exemplo, você pode iniciar a orquestração enviando a seguinte solicitação HTTP POST:
POST http://{host}/orchestrators/E4_SmsPhoneVerification
Content-Length: 14
Content-Type: application/json
"+1425XXXXXXX"
HTTP/1.1 202 Accepted
Content-Length: 695
Content-Type: application/json; charset=utf-8
Location: http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
{"id":"741c65651d4c40cea29acdd5bb47baf1","statusQueryGetUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}","sendEventPostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}","terminatePostUri":"http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/terminate?reason={text}&taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}"}
A função orquestrador recebe o número de telefone fornecido e envia-lhe imediatamente uma mensagem SMS com um código de verificação de 4 dígitos gerado aleatoriamente — por exemplo, 2168. Em seguida, a função aguarda 90 segundos por uma resposta.
Para responder com o código, você pode usar RaiseEventAsync
(.NET) ou raiseEvent
(JavaScript/TypeScript) dentro de outra função ou invocar o webhook HTTP POST sendEventPostUri referenciado na resposta 202 acima, substituindo {eventName}
pelo nome do evento, SmsChallengeResponse
:
POST http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1/raiseEvent/SmsChallengeResponse?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
Content-Length: 4
Content-Type: application/json
2168
Se você enviar isso antes que o temporizador expire, a orquestração será concluída e o output
campo será definido como true
, indicando uma verificação bem-sucedida.
GET http://{host}/runtime/webhooks/durabletask/instances/741c65651d4c40cea29acdd5bb47baf1?taskHub=DurableFunctionsHub&connection=Storage&code={systemKey}
HTTP/1.1 200 OK
Content-Length: 144
Content-Type: application/json; charset=utf-8
{"runtimeStatus":"Completed","input":"+1425XXXXXXX","output":true,"createdTime":"2017-06-29T19:10:49Z","lastUpdatedTime":"2017-06-29T19:12:23Z"}
Se você deixar o temporizador expirar, ou se você inserir o código errado quatro vezes, você pode consultar o status e ver uma saída de false
função de orquestração, indicando que a verificação do telefone falhou.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 145
{"runtimeStatus":"Completed","input":"+1425XXXXXXX","output":false,"createdTime":"2017-06-29T19:20:49Z","lastUpdatedTime":"2017-06-29T19:22:23Z"}
Próximos passos
Este exemplo demonstrou algumas das capacidades avançadas das Funções Duráveis, nomeadamente WaitForExternalEvent
e CreateTimer
APIs. Você viu como eles podem ser combinados com Task.WaitAny
(C#)/context.df.Task.any
(JavaScript/TypeScript)/context.task_any
(Python) para implementar um sistema de tempo limite confiável, que muitas vezes é útil para interagir com pessoas reais. Você pode aprender mais sobre como usar funções duráveis lendo uma série de artigos que oferecem uma cobertura aprofundada de tópicos específicos.