Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Il modello di interazione umana descrive i flussi di lavoro che sospendono e attendono l'input da una persona prima di continuare. Il modello è utile per i flussi di lavoro di approvazione, l'autenticazione a più fattori e qualsiasi scenario in cui una persona risponde entro un limite di tempo.
Questo esempio illustra come creare un'orchestrazione Durable Functions che include l'interazione umana. L'esempio implementa un sistema di verifica telefono basato su SMS. È comune nei flussi di verifica dei numeri di telefono e dell'autenticazione a più fattori (MFA).
Annotazioni
La versione 4 del modello di programmazione Node.js per Azure Functions è disponibile a livello generale. Il modello v4 è progettato per offrire un'esperienza più flessibile e intuitiva per sviluppatori JavaScript e TypeScript. Per altre informazioni sulle differenze tra v3 e v4, vedere la guida alla migrazione.
Nei frammenti di codice seguenti JavaScript (PM4) indica il modello di programmazione v4, la nuova esperienza.
Prerequisiti
Questo articolo illustra come implementare il modello di interazione umana usando gli SDK di Durable Task. Nell'esempio viene implementato un flusso di lavoro di approvazione in cui un'orchestrazione attende che una persona approvi o rifiuti una richiesta prima che continui.
Panoramica dello scenario
La verifica telefonica consente di verificare che le persone che usano l'app non siano spammer e che controllino il numero di telefono fornito. L'autenticazione a più fattori è un modo comune per proteggere gli account. La creazione di una verifica telefono personalizzata richiede un'interazione con stato con una persona. Un utente ottiene in genere un codice (ad esempio, un numero a quattro cifre) e deve rispondere in un periodo di tempo ragionevole.
I Azure Functions standard sono senza stato (come molti altri endpoint cloud), quindi questo tipo di interazione richiede di archiviare lo stato in un database o in un altro archivio permanente. È anche possibile suddividere l'interazione tra più funzioni e coordinarle. Ad esempio, una funzione genera un codice, lo archivia e lo invia al telefono dell'utente. Un'altra funzione riceve la risposta dell'utente e la mappa alla richiesta originale per convalidare il codice. Aggiungere un timeout per proteggere la sicurezza. Questo flusso di lavoro diventa rapidamente complesso.
Durable Functions riduce la complessità di questo scenario. In questo esempio, una funzione di orchestrazione gestisce l'interazione stateful senza un archivio dati esterno. Poiché le funzioni dell'agente di orchestrazione sono durevoli, questi flussi interattivi sono altamente affidabili.
I flussi di lavoro di approvazione sono comuni nelle applicazioni aziendali in cui una richiesta deve essere esaminata da un essere umano prima di procedere. I requisiti del flusso di lavoro sono:
- Attendere indefinitamente per una risposta umana o fino a un timeout
- Gestire sia i risultati di approvazione che di rifiuto
- Timeout di supporto quando non viene ricevuta alcuna risposta
- Tenere traccia dello stato in modo che il richiedente possa controllare lo stato di avanzamento
Gli SDK per attività durevoli semplificano questo scenario con:
- Eventi esterni: l'orchestrazione può sospendere e attendere un evento generato da un sistema esterno o da un utente
- Timer durevoli: impostare un timeout che si attiva se non viene ricevuta alcuna risposta
- Stato personalizzato: tenere traccia ed esporre lo stato corrente del flusso di lavoro ai client
Configurare l'integrazione di Twilio
Questo esempio prevede l'uso del servizio Twilio per inviare messaggi SMS al telefono cellulare. Azure Functions ha già supporto per Twilio tramite l'associazione 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 seguenti tre impostazioni dell'app alla propria app per le funzioni.
| Nome dell'impostazione dell'app | Descrizione del 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. |
L'orchestratore e le attività
Questo articolo illustra le funzioni seguenti nell'app di esempio:
-
E4_SmsPhoneVerification: funzione di orchestrazione che esegue il processo di verifica del telefono e gestisce timeout e tentativi. -
E4_SendSmsChallenge: funzione di attività che invia un codice tramite SMS.
Annotazioni
La HttpStart funzione nell'app di esempio e la procedura di avvio rapido funge da client di orchestrazione e attiva la funzione di orchestrazione.
Questo articolo illustra i componenti seguenti nell'app di esempio:
-
ApprovalOrchestration/approvalOrchestrator/human_interaction_orchestrator: agente di orchestrazione che invia una richiesta di approvazione e attende una risposta umana o un timeout. -
SubmitApprovalRequestActivity/submitRequest/submit_approval_request: un'attività che notifica a un approvatore umano, ad esempio tramite email o messaggio di chat. -
ProcessApprovalActivity/processApproval/process_approval: attività che elabora la decisione di approvazione.
Orchestrator
Funzione di orchestrazione E4_SmsPhoneVerification
[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;
}
}
Annotazioni
Potrebbe non essere ovvio in un primo momento, ma questo agente di orchestrazione non viola il vincolo di orchestrazione deterministico. È deterministico perché la CurrentUtcDateTime proprietà calcola l'ora di scadenza del timer e restituisce lo stesso valore in ogni riproduzione in questo punto nel codice dell'agente di orchestrazione. Questo comportamento garantisce che winner sia lo stesso per ogni chiamata ripetuta a Task.WhenAny.
Una volta avviata, questa funzione di orchestrazione esegue le seguenti operazioni:
- Ottiene un numero di telefono a cui inviare la notifica SMS.
- Chiama E4_SendSmsChallenge per inviare un messaggio SMS all'utente e restituisce il codice di verifica a quattro cifre previsto.
- Crea un timer durevole che attiva 90 secondi dopo l'ora corrente.
- In parallelo con il timer, si attende un evento SmsChallengeResponse da parte dell'utente.
L'utente riceve un messaggio SMS con un codice di quattro cifre Hanno 90 secondi per inviare lo stesso codice all'istanza orchestratore per completare la verifica. Se inviano il codice errato, ottengono altri tre tentativi nella stessa finestra di 90 secondi.
Avviso
Annullare i timer non più necessari. Nell'esempio precedente, l'orchestrazione annulla il timer quando accetta una risposta alla sfida.
L'agente di orchestrazione invia una richiesta di approvazione, quindi attende una risposta umana o un timeout.
using Microsoft.DurableTask;
using System;
using System.Threading;
using System.Threading.Tasks;
[DurableTask(nameof(ApprovalOrchestration))]
public class ApprovalOrchestration : TaskOrchestrator<ApprovalRequestData, ApprovalResult>
{
public override async Task<ApprovalResult> RunAsync(
TaskOrchestrationContext context, ApprovalRequestData input)
{
string requestId = input.RequestId;
double timeoutHours = input.TimeoutHours;
// Step 1: Submit the approval request (notify approver)
SubmissionResult submissionResult = await context.CallActivityAsync<SubmissionResult>(
nameof(SubmitApprovalRequestActivity), input);
// Make the status available via custom status
context.SetCustomStatus(submissionResult);
// Step 2: Create a durable timer for the timeout
DateTime timeoutDeadline = context.CurrentUtcDateTime.AddHours(timeoutHours);
using var timeoutCts = new CancellationTokenSource();
Task timeoutTask = context.CreateTimer(timeoutDeadline, timeoutCts.Token);
// Step 3: Wait for an external event (approval/rejection)
Task<ApprovalResponseData> approvalTask = context.WaitForExternalEvent<ApprovalResponseData>(
"approval_response");
// Step 4: Wait for either the timeout or the approval response
Task completedTask = await Task.WhenAny(approvalTask, timeoutTask);
// Step 5: Process based on which task completed
ApprovalResult result;
if (completedTask == approvalTask)
{
// Human responded in time - cancel the timeout timer
timeoutCts.Cancel();
ApprovalResponseData approvalData = approvalTask.Result;
// Process the approval
result = await context.CallActivityAsync<ApprovalResult>(
nameof(ProcessApprovalActivity),
new ProcessApprovalInput
{
RequestId = requestId,
IsApproved = approvalData.IsApproved,
Approver = approvalData.Approver
});
}
else
{
// Timeout occurred
result = new ApprovalResult
{
RequestId = requestId,
Status = "Timeout",
ProcessedAt = context.CurrentUtcDateTime.ToString("o")
};
}
return result;
}
}
Questo agente di orchestrazione esegue le azioni seguenti:
- Invia la richiesta di approvazione chiamando un'attività che notifica al responsabile dell'approvazione.
- Imposta lo stato personalizzato in modo che i client possano tenere traccia dello stato di avanzamento.
- Crea un timer durevole per la scadenza del timeout.
- Attende un evento esterno (
approval_response) che l'approvatore alza. - Usa
WhenAny,when_anyoanyOfper attendere quella che si completa per prima: l'approvazione o il timeout. - Elabora il risultato in base all'attività completata.
Avviso
Annullare i timer non più necessari. Nell'esempio C# l'orchestrazione annulla il timer di timeout quando riceve l'approvazione.
Attività
Funzione dell’attività E4_SendSmsChallenge
La funzione E4_SendSmsChallenge usa l'associazione Twilio per inviare un messaggio SMS che include un codice a quattro cifre all'utente.
[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;
}
Annotazioni
Per eseguire l'esempio, installare il pacchetto NuGet Microsoft.Azure.WebJobs.Extensions.Twilio. Non installare il pacchetto NuGet Twilio principale perché può causare conflitti di versione ed errori di compilazione.
Le attività inviano la richiesta di approvazione ed elaborano la risposta.
Invia l'attività di richiesta di approvazione
using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
[DurableTask(nameof(SubmitApprovalRequestActivity))]
public class SubmitApprovalRequestActivity : TaskActivity<ApprovalRequestData, SubmissionResult>
{
private readonly ILogger<SubmitApprovalRequestActivity> _logger;
public SubmitApprovalRequestActivity(ILogger<SubmitApprovalRequestActivity> logger)
{
_logger = logger;
}
public override Task<SubmissionResult> RunAsync(
TaskActivityContext context, ApprovalRequestData input)
{
_logger.LogInformation(
"Submitting approval request {RequestId} from {Requester} for {Item}",
input.RequestId, input.Requester, input.Item);
// In a real system, this would send an email, notification, or update a database
var result = new SubmissionResult
{
RequestId = input.RequestId,
Status = "Pending",
SubmittedAt = DateTime.UtcNow.ToString("o"),
ApprovalUrl = $"http://localhost:8000/api/approvals/{input.RequestId}"
};
return Task.FromResult(result);
}
}
Attività di approvazione del processo
using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
[DurableTask(nameof(ProcessApprovalActivity))]
public class ProcessApprovalActivity : TaskActivity<ProcessApprovalInput, ApprovalResult>
{
private readonly ILogger<ProcessApprovalActivity> _logger;
public ProcessApprovalActivity(ILogger<ProcessApprovalActivity> logger)
{
_logger = logger;
}
public override Task<ApprovalResult> RunAsync(
TaskActivityContext context, ProcessApprovalInput input)
{
string status = input.IsApproved ? "Approved" : "Rejected";
_logger.LogInformation(
"Processing {Status} request {RequestId} by {Approver}",
status, input.RequestId, input.Approver);
// In a real system, this would update a database, trigger workflows, etc.
var result = new ApprovalResult
{
RequestId = input.RequestId,
Status = status,
ProcessedAt = DateTime.UtcNow.ToString("o"),
Approver = input.Approver
};
return Task.FromResult(result);
}
}
// Data classes
public class ApprovalRequestData
{
public string RequestId { get; set; } = string.Empty;
public string Requester { get; set; } = string.Empty;
public string Item { get; set; } = string.Empty;
public double TimeoutHours { get; set; } = 24.0;
}
public class ApprovalResponseData
{
public bool IsApproved { get; set; }
public string Approver { get; set; } = string.Empty;
}
public class SubmissionResult
{
public string RequestId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string SubmittedAt { get; set; } = string.Empty;
public string ApprovalUrl { get; set; } = string.Empty;
}
public class ProcessApprovalInput
{
public string RequestId { get; set; } = string.Empty;
public bool IsApproved { get; set; }
public string Approver { get; set; } = string.Empty;
}
public class ApprovalResult
{
public string RequestId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string ProcessedAt { get; set; } = string.Empty;
public string? Approver { get; set; }
}
Esegui l'esempio
Usare le funzioni attivate da HTTP nell'esempio per avviare l'orchestrazione inviando la richiesta HTTP POST seguente:
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}"}
La funzione dell'agente di orchestrazione riceve il numero di telefono e invia immediatamente un messaggio SMS a tale numero con un codice di verifica a 4 cifre generato in modo casuale, 2168ad esempio . La funzione attende quindi 90 secondi per ricevere una risposta.
Per rispondere con il codice, usare RaiseEventAsync (.NET) o raiseEvent (JavaScript e TypeScript) in un'altra funzione oppure chiamare sendEventPostUri endpoint HTTP POST nella risposta 202. Sostituire {eventName} con 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 invii l'evento prima della scadenza del timer, l'orchestrazione viene completata e il campo viene impostato su output, che indica una verifica riuscita.
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 il timer scade o si inserisce il codice errato quattro volte, eseguire una query di stato per vedere output impostato su false, che indica che la verifica del telefono non è riuscita.
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"}
Per eseguire l'esempio:
Avvia l'emulatore del Durable Task Scheduler per lo sviluppo locale.
docker run -d -p 8080:8080 -p 8082:8082 --name dts-emulator mcr.microsoft.com/dts/dts-emulator:latestAvvia il worker per registrare l'orchestratore e le attività.
Eseguire il client per pianificare un flusso di lavoro di approvazione e inviare eventi.
using System;
using System.Threading.Tasks;
var client = DurableTaskClientBuilder.UseDurableTaskScheduler(connectionString).Build();
// Schedule the approval workflow
var input = new ApprovalRequestData
{
RequestId = "request-" + Guid.NewGuid().ToString(),
Requester = "john.doe@example.com",
Item = "Vacation Request - 5 days",
TimeoutHours = 24
};
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(ApprovalOrchestration), input);
Console.WriteLine($"Started approval workflow: {instanceId}");
// Simulate human approving the request
Console.WriteLine("Simulating approval...");
await Task.Delay(2000);
// Raise the approval event
var approvalResponse = new ApprovalResponseData
{
IsApproved = true,
Approver = "manager@example.com"
};
await client.RaiseEventAsync(instanceId, "approval_response", approvalResponse);
// Wait for completion
var result = await client.WaitForInstanceCompletionAsync(instanceId, getInputsAndOutputs: true);
Console.WriteLine($"Result: {result.ReadOutputAs<ApprovalResult>().Status}");
Passaggi successivi
Questo esempio illustra le funzionalità avanzate di Durable Functions, incluse le API /> /Task.WhenAny (C#), context.df.Task.any (JavaScript e TypeScript) o context.task_any (Python) per implementare un modello di timeout affidabile per i flussi di lavoro che attendono la risposta degli utenti. Altre informazioni sulle Durable Functions in una serie di articoli che trattano argomenti specifici.
Questo esempio illustra come usare gli SDK di Durable Task per implementare flussi di lavoro che attendono che gli utenti rispondano, con timeout configurabili. Concetti chiave:
Eventi esterni: uso di
WaitForExternalEventper attendere l'inputTimer durevoli: utilizzo di
CreateTimerper implementare i timeoutAttività di corsa: uso
WhenAnydi ,when_anyoanyOfper gestire qualsiasi attività venga completata per prima