Orchestrazioni durevoli

Durable Functions è un'estensione di Funzioni di Azure. È possibile usare una funzione di orchestrazione per orchestrare l'esecuzione di altre istanze di Durable Functions all'interno di un'app per le funzioni. Le funzioni dell'agente di orchestrazione hanno le caratteristiche seguenti:

  • Le funzioni dell'agente di orchestrazione definiscono i flussi di lavoro delle funzioni tramite codice procedurale. Non sono necessari schemi dichiarativi o finestre di progettazione.
  • Le funzioni dell'agente di orchestrazione possono chiamare altre funzioni durevoli in modo sincrono e asincrono. L'output delle funzioni chiamate può essere salvato in modo affidabile in variabili locali.
  • Le funzioni dell'agente di orchestrazione sono durevoli e affidabili. Lo stato di avanzamento dell'esecuzione viene automaticamente sottoposto a checkpoint quando la funzione è in attesa o è sospesa. Lo stato locale non va mai perso quando il processo viene riciclato o la macchina virtuale viene riavviata.
  • Le funzioni dell'agente di orchestrazione possono essere a esecuzione prolungata. La durata totale di un'istanza di orchestrazione può essere di alcuni secondi, giorni, mesi o infinita.

Questo articolo contiene una panoramica delle funzioni dell'agente di orchestrazione, con informazioni su come usarle per risolvere vari problemi legati allo sviluppo di app. Se non si ha familiarità con i tipi di funzioni disponibili in un'app di Durable Functions, vedere prima l'articolo Tipi di app di Durable Functions.

Identità di orchestrazione

Ogni istanza di un'orchestrazione ha un identificatore di istanza, chiamato anche ID istanza. Per impostazione predefinita, ogni ID istanza è un GUID generato automaticamente. Tuttavia, gli ID istanza possono anche essere qualsiasi valore stringa generato dall'utente. Ogni ID istanza di orchestrazione deve essere univoco all'interno di un hub attività.

Di seguito sono riportate alcune regole sugli ID istanza:

  • Gli ID istanza devono essere compresi tra 1 e 100 caratteri.
  • Gli ID istanza non possono iniziare con @.
  • Gli ID istanza non possono contenere i caratteri /, \, # o ?.
  • Gli ID istanza non possono contenere caratteri di controllo.

Nota

In generale, è consigliabile usare ID istanza generati automaticamente laddove possibile. Gli ID istanza generati dall'utente sono destinati a scenari in cui esiste un mapping uno a uno tra un'istanza di orchestrazione e un'entità esterna specifica dell'applicazione, come un ordine di acquisto o un documento.

Inoltre, l'applicazione effettiva delle regole di restrizione dei caratteri può variare a seconda del provider di archiviazione usato dall'app. Per garantire il comportamento e la compatibilità corretti, è consigliabile seguire le regole dell'ID istanza elencate in precedenza.

L'ID istanza di un'orchestrazione è un parametro obbligatorio per la maggior parte delle operazioni di gestione di istanze. È anche importante per la diagnostica, ad esempio per le ricerche all'interno dei dati di tracciabilità dell'orchestrazione in Application Insights a scopi di analisi o risoluzione dei problemi. Per questo motivo, è consigliabile salvare gli ID istanza generati in una posizione esterna, ad esempio un database o nei log applicazioni, in cui è possibile farvi facilmente riferimento in un secondo momento.

Affidabilità

Le funzioni dell'agente di orchestrazione mantengono in modo affidabile il proprio stato di esecuzione tramite il modello progettuale Event Sourcing. Invece di archiviare direttamente lo stato corrente di un'orchestrazione, Durable Task Framework usa un archivio di solo accodamento per registrare la serie completa di azioni eseguite dall'orchestrazione di funzioni. Rispetto al "dump" dello stato del runtime completo, l'archivio di solo accodamento presenta diversi vantaggi, in termini di prestazioni, scalabilità e reattività. Inoltre, consente di ottenere coerenza finale per i dati transazionali, oltre a dati di cronologia e audit trail completi, che supportano azioni di compensazione affidabili.

Durable Functions usa l'origine eventi in modo trasparente. Dietro le quinte, l'operatore await (C#) o yield (JavaScript/Python) in una funzione di orchestrazione restituisce il controllo del thread di orchestrazione al dispatcher Durable Task Framework. Nel caso di Java, non esiste una parola chiave del linguaggio speciale. La chiamata .await() a un'attività restituirà invece il controllo al dispatcher tramite un oggetto personalizzato Throwable. Il dispatcher esegue quindi il commit di eventuali nuove azioni pianificate dalla funzione di orchestrazione, ad esempio la chiamata di una o più funzioni figlio o la pianificazione di un timer durevole, nell'archiviazione. L'azione di commit trasparente aggiorna la cronologia di esecuzione dell'istanza di orchestrazione aggiungendo tutti i nuovi eventi nell'archiviazione, in modo analogo a un log di sola accodamento. Analogamente, l'azione di commit crea messaggi nella risorsa di archiviazione per pianificare il lavoro effettivo. A questo punto, la funzione di orchestrazione può essere scaricata dalla memoria. Per impostazione predefinita, Durable Functions usa Archiviazione di Azure come archivio dello stato di runtime, ma sono supportati anche altri provider di archiviazione.

Quando a una funzione di orchestrazione vengono assegnate altre operazioni da eseguire (ad esempio, si riceve un messaggio di risposta oppure un timer durevole scade), l'agente di orchestrazione si riattiva ed esegue nuovamente l'intera funzione dall'inizio per ricreare lo stato locale. Durante la riproduzione, se il codice tenta di chiamare una funzione (o di eseguire qualsiasi altro lavoro asincrono), Durable Task Framework consulta la cronologia di esecuzione dell'orchestrazione corrente. Se rileva che la funzione di attività è già stata eseguita e ha restituito un risultato, riesegue il risultato della funzione, mentre il codice dell'agente di orchestrazione continua a essere eseguito. La riproduzione continua fino al completamento del codice della funzione o finché non viene pianificata una nuova operazione asincrona.

Nota

Per il funzionamento corretto e affidabile del modello di riesecuzione, il codice della funzione dell'agente di orchestrazione deve essere deterministico. Il codice dell'agente di orchestrazione non deterministico può causare errori di runtime o altri comportamenti imprevisti. Per altre informazioni sulle restrizioni del codice per le funzioni dell'agente di orchestrazione, vedere la documentazione relativa ai vincoli di codice della funzione dell'agente di orchestrazione.

Nota

Se una funzione dell'agente di orchestrazione emette messaggi di log, la riesecuzione potrebbe causare l'emissione di duplicati. Per altre informazioni sui motivi per cui si verifica questo comportamento e su come correggerlo, vedere l'argomento Registrazione.

Cronologia di orchestrazione

Il comportamento di Event Sourcing di Durable Task Framework è strettamente collegato al codice delle funzioni dell'agente di orchestrazione che si scrive. Si supponga di avere una funzione dell'agente di orchestrazione per il concatenamento di attività, come quella riportata di seguito:

Nota

La versione 4 del modello di programmazione Node.js per Funzioni di Azure è disponibile a livello generale. Il nuovo modello v4 è progettato per offrire un'esperienza più flessibile e intuitiva per gli sviluppatori JavaScript e TypeScript. Altre informazioni sulle differenze tra v3 e v4 sono disponibili nella guida alla migrazione.

Nei frammenti di codice seguenti JavaScript (PM4) indica il modello di programmazione V4, la nuova esperienza.

[FunctionName("HelloCities")]
public static async Task<List<string>> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));

    // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
    return outputs;
}

Ogni volta che viene pianificata una funzione di attività, Durable Task Framework esegue il checkpoint dello stato di esecuzione della funzione in un back-end di archiviazione durevole (archiviazione tabelle di Azure per impostazione predefinita). Questo stato è detto cronologia dell'orchestrazione.

Tabella di cronologia

A ogni checkpoint, il framework di attività permanenti esegue in genere queste operazioni:

  1. Salva la cronologia di esecuzione in una risorsa di archiviazione durevole.
  2. Accoda i messaggi per le funzioni che l'agente di orchestrazione intende richiamare.
  3. Accoda i messaggi per l'agente di orchestrazione stesso, ad esempio messaggi timer durevoli.

Dopo aver completato l'impostazione dei checkpoint, la funzione dell'agente di orchestrazione può essere rimossa dalla memoria fino a quando non siano ancora presenti operazioni che deve eseguire.

Nota

Archiviazione di Azure non offre alcuna garanzia durante le transazioni tra il salvataggio dei dati in code e in tabelle di archiviazione. Per gestire gli errori, il provider di funzioni permanenti Archiviazione di Azure usa modelli di coerenza finale. Tali modelli garantiscono che non viene perso alcun dato se si verifica un arresto anomalo del sistema o la perdita di connettività durante un checkpoint. I provider di archiviazione alternativi, ad esempio il provider di archiviazione MSSQL di Durable Functions, possono offrire garanzie di coerenza più avanzate.

Al termine dell'operazione, la cronologia della funzione illustrata in precedenza ha un aspetto simile al seguente nell'archiviazione tabelle di Azure (abbreviata a scopo illustrativo):

PartitionKey (InstanceId) EventType Timestamp: Input Nome Risultato Status
eaee885b ExecutionStarted 2021-05-05T18:45:28.852Z Null HelloCities
eaee885b OrchestratorStarted 2021-05-05T18:45:32.362Z
eaee885b TaskScheduled 2021-05-05T18:45:32.670Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:32.670Z
eaee885b TaskCompleted 2021-05-05T18:45:34.201Z """Hello Tokyo!"""
eaee885b OrchestratorStarted 2021-05-05T18:45:34.232Z
eaee885b TaskScheduled 2021-05-05T18:45:34.435Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:34.435Z
eaee885b TaskCompleted 2021-05-05T18:45:34.763Z """Hello Seattle!"""
eaee885b OrchestratorStarted 2021-05-05T18:45:34.857Z
eaee885b TaskScheduled 2021-05-05T18:45:34.857Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:34.857Z
eaee885b TaskCompleted 2021-05-05T18:45:34.919Z """Hello London!"""
eaee885b OrchestratorStarted 2021-05-05T18:45:35.032Z
eaee885b OrchestratorCompleted 2021-05-05T18:45:35.044Z
eaee885b ExecutionCompleted 2021-05-05T18:45:35.044Z "[""Hello Tokyo!"",""Hello Seattle!"",""Hello London!""]" Completato

Di seguito vengono indicate alcune note sui valori di colonna.

  • PartitionKey: contiene l'ID istanza dell'orchestrazione.
  • EventType: rappresenta uno dei tipi di evento Qui è possibile trovare descrizioni dettagliate di tutti i tipi di eventi di cronologia.
  • Timestamp: timestamp UTC dell'evento di cronologia.
  • Name: nome della funzione richiamata.
  • Input: input in formato JSON della funzione.
  • Result: output della funzione, ovvero il valore restituito.

Avviso

Benché sia utile come strumento di debug, usare con cautela questa tabella che può cambiare con l'evoluzione dell'estensione Funzioni permanenti.

Ogni volta che la funzione viene ripresa dopo l'attesa del completamento di un'attività, Durable Task Framework esegue nuovamente la funzione dell'agente di orchestrazione da zero. In ogni riesecuzione, consulta la cronologia di esecuzione per determinare se l'attività asincrona corrente è stata completata. Se la cronologia di esecuzione indica che l'attività è già stata completata, il framework riproduce l'output di tale attività e passa all'attività successiva. Questo processo continua fino a quando non viene riprodotta l'intera cronologia di esecuzione. Dopo aver riprodotto la cronologia di esecuzione corrente, le variabili locali verranno ripristinate nei valori precedenti.

Funzionalità e modelli

Le sezioni successive descrivono le funzionalità e i modelli delle funzioni dell'agente di orchestrazione.

Orchestrazioni secondarie

Oltre a chiamare le funzioni di attività, le funzioni dell'agente di orchestrazione possono chiamare anche altre funzioni dell'agente di orchestrazione. Ad esempio, è possibile compilare un'orchestrazione più grande da una libreria di funzioni di orchestrazione. Oppure, eseguire più istanze di una funzione di orchestrazione in parallelo.

Per altre informazioni e per alcuni esempi, vedere l'articolo sulle orchestrazioni secondarie.

Timer durevoli

Le orchestrazioni possono pianificare timer durevoli per implementare ritardi oppure per configurare la gestione dei timeout per azioni asincrone. Usare timer durevoli nelle funzioni dell'agente di orchestrazione anziché nelle API "sospensione" native del linguaggio.

Per altre informazioni e per alcuni esempi, vedere l'articolo sui timer durevoli.

Eventi esterni

Le funzioni di orchestrazione possono attendere eventi esterni per aggiornare un'istanza di orchestrazione. Questa funzionalità di Durable Functions è spesso utile per gestire l'interazione umana o altri callback esterni.

Per altre informazioni e per alcuni esempi, vedere l'articolo sugli eventi esterni.

Gestione degli errori

Le funzioni dell'agente di orchestrazione possono usare le funzionalità di gestione degli errori del linguaggio di programmazione. Gli attuali modelli come try/catch sono supportati nel codice di orchestrazione.

Le funzioni dell'agente di orchestrazione possono anche aggiungere criteri di ripetizione alle funzioni di attività o dell'agente di orchestrazione secondario che chiamano. Se una funzione di attività o dell'agente di orchestrazione secondario restituisce un errore con un'eccezione, il criterio di ripetizione specificato può ritardare e ripetere automaticamente l'esecuzione fino a un numero specificato di volte.

Nota

Se si verifica un'eccezione non gestita in una funzione dell'agente di orchestrazione, l'istanza di orchestrazione verrà completata con lo stato Failed. Le istanze di orchestrazione che non riescono non possono essere ripetute.

Per altre informazioni e per alcuni esempi, vedere l'articolo sulla gestione degli errori.

Sezioni critiche (Durable Functions 2.x, attualmente solo .NET)

Le istanze di orchestrazione sono a thread singolo, quindi non bisogna preoccuparsi di eventuali race condition all'interno di un'orchestrazione. Tuttavia, le race condition sono possibili quando le orchestrazioni interagiscono con i sistemi esterni. Per mitigare le race condition durante l'interazione con sistemi esterni, le funzioni dell'agente di orchestrazione definiscono sezioni critiche usando un metodo LockAsync in .NET.

L'esempio di codice seguente mostra una funzione dell'agente di orchestrazione che definisce una sezione critica. Accede alla sezione critica usando il metodo LockAsync. Questo metodo richiede il passaggio di uno o più riferimenti a un'entità durevole, che gestisce in modo durevole lo stato di blocco. Il codice della sezione critica può essere eseguito solo da una singola istanza di questa orchestrazione alla volta.

[FunctionName("Synchronize")]
public static async Task Synchronize(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var lockId = new EntityId("LockEntity", "MyLockIdentifier");
    using (await context.LockAsync(lockId))
    {
        // critical section - only one orchestration can enter at a time
    }
}

Il metodo LockAsync acquisisce i blocchi durevoli e restituisce IDisposable che termina la sezione critica all'eliminazione. Questo risultato IDisposable può essere usato insieme a un blocco using per ottenere una rappresentazione sintattica della sezione critica. Quando una funzione dell'agente di orchestrazione accede a una sezione critica, questo blocco di codice può essere eseguito da una sola istanza. Tutte le altre istanze che provano ad accedere alla sezione critica verranno bloccate finché l'istanza precedente non ne esce.

La funzionalità delle sezioni critiche è anche utile per coordinare le modifiche delle entità durevoli. Per altre informazioni sulle sezioni critiche, vedere l'argomento sul coordinamento di entità durevoli.

Nota

Le sezioni critiche sono disponibili in Durable Functions 2.0. Attualmente, solo le orchestrazioni in-proc .NET implementano questa funzionalità. Le entità e le sezioni critiche non sono ancora disponibili in Durable Functions per il ruolo di lavoro isolato dotnet.

Chiamata agli endpoint HTTP (Durable Functions 2. x)

Le funzioni dell'agente di orchestrazione non possono eseguire operazioni di I/O, come descritto nell'articolo sui vincoli di codice delle funzioni dell'agente di orchestrazione. La soluzione alternativa tipica per questa limitazione consiste nell'eseguire il wrapping di qualsiasi codice che richiede l'I/O in una funzione di attività. Le orchestrazioni che interagiscono con sistemi esterni usano spesso le funzioni di attività per effettuare chiamate HTTP e restituire il risultato all'orchestrazione.

Per semplificare questo modello comune, le funzioni dell'agente di orchestrazione possono usare il metodo CallHttpAsync per richiamare direttamente le API HTTP.

[FunctionName("CheckSiteAvailable")]
public static async Task CheckSiteAvailable(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    Uri url = context.GetInput<Uri>();

    // Makes an HTTP GET request to the specified endpoint
    DurableHttpResponse response = 
        await context.CallHttpAsync(HttpMethod.Get, url);

    if ((int)response.StatusCode == 400)
    {
        // handling of error codes goes here
    }
}

Oltre a modelli di richiesta/risposta di base, il metodo supporta la gestione automatica di comuni modelli di polling HTTP 202 asincroni, nonché l'autenticazione con servizi esterni tramite identità gestite.

Per altre informazioni e per alcuni esempi dettagliati, vedere l'articolo sulle funzionalità HTTP.

Nota

La chiamata agli endpoint HTTP direttamente dalle funzioni dell'agente di orchestrazione è disponibile in Durable Functions 2.0 e versioni successive.

Passaggio di più parametri

Non è possibile passare più parametri direttamente a una funzione di attività. In questo caso è consigliabile passare una matrice di oggetti o oggetti compositi.

In .NET è anche possibile usare oggetti ValueTuple. L'esempio seguente usa le nuove funzionalità di ValueTuple aggiunte con C# 7:

[FunctionName("GetCourseRecommendations")]
public static async Task<object> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string major = "ComputerScience";
    int universityYear = context.GetInput<int>();

    object courseRecommendations = await context.CallActivityAsync<object>(
        "CourseRecommendations",
        (major, universityYear));
    return courseRecommendations;
}

[FunctionName("CourseRecommendations")]
public static async Task<object> Mapper([ActivityTrigger] IDurableActivityContext inputs)
{
    // parse input for student's major and year in university
    (string Major, int UniversityYear) studentInfo = inputs.GetInput<(string, int)>();

    // retrieve and return course recommendations by major and university year
    return new
    {
        major = studentInfo.Major,
        universityYear = studentInfo.UniversityYear,
        recommendedCourses = new []
        {
            "Introduction to .NET Programming",
            "Introduction to Linux",
            "Becoming an Entrepreneur"
        }
    };
}

Passaggi successivi