Impostare ed eseguire query sullo stato di orchestrazione personalizzato

Lo stato di orchestrazione personalizzato consente di collegare metadati JSON arbitrari a un'istanza di orchestrazione in esecuzione in modo che i client esterni possano eseguirne query in qualsiasi momento. Usare lo stato personalizzato quando è necessario:

  • Segnalare lo stato di avanzamento a metà del volo : consente a un'interfaccia utente di mostrare il passaggio raggiunto da un'orchestrazione senza attendere il completamento.
  • Restituisci dati dinamici ai chiamanti — visualizza suggerimenti, informazioni sullo sconto o istruzioni sui prossimi passaggi mentre l'orchestrazione è ancora in esecuzione.
  • Coordinarsi con i sistemi esterni — condividere lo stato che altri servizi o operatori umani possono monitorare e su cui possono agire.

Avvertimento

Il payload di stato personalizzato è limitato a 16 KB di testo JSON UTF-16. Se hai bisogno di un payload più grande, usa l'archiviazione esterna e memorizza un riferimento (ad esempio un Blob URL) nello stato personalizzato.

In Funzioni di Azure questo stato è disponibile tramite l'API HTTP GetStatus o l'API SDK equivalente nell'oggetto client di orchestrazione.

Negli SDK Durable Task, puoi accedere a questo stato tramite le API di metadati di orchestrazione dell'SDK (ad esempio, GetInstanceAsync in .NET, getInstanceMetadata in Java o FetchOrchestrationMetadata in Go). Gli orchestratori Go impostano uno stato personalizzato tipizzato con ctx.SetCustomStatusValue e ne controllano l'errore. L'esempio Go continue-as-new riporta il suo conteggio ciclico in questo modo.

Importante

Attualmente, Durable Task SDK di PowerShell non è disponibile.

Casi d'uso di esempio per lo stato di orchestrazione personalizzato

La tabella seguente riepiloga i modelli comuni. Selezionare un caso d'uso per passare all'esempio corrispondente.

caso d'uso Descrizione
Visualizzare lo stato di orchestrazione Aggiornare una stringa o un oggetto dopo ogni attività in modo che i client possano visualizzare un indicatore di stato.
Restituire metadati dinamici ai client Impostare dati strutturati (ad esempio raccomandazioni) di cui i client eseguono il rendering senza che siano necessari endpoint lato server personalizzati.
Fornire dati interattivi ai client Mettere in evidenza gli URL di prenotazione, le informazioni sullo sconto o le istruzioni successive che i client seguono mentre l'orchestrazione attende un evento esterno.
Interrogare stato personalizzato Leggere il valore di stato personalizzato da un client usando le API HTTP o le chiamate SDK.

Visualizzare i progressi dell'orchestrazione

In questo modello, l'orchestratore chiama SetCustomStatus (o l'equivalente nella tua lingua) dopo il completamento di ogni attività, aggiornando lo stato con il nome dell'ultima città completata. Un client esegue il polling dell'endpoint di stato, ne legge il valore attuale e aggiorna un indicatore di avanzamento nell'interfaccia utente.

L'esempio seguente illustra lo stato di avanzamento della condivisione usando l'endpoint di stato HTTP Durable Functions:

Annotazioni

Questi esempi vengono scritti per Durable Functions 2.x e non sono compatibili con Durable Functions 1.x. Per altre informazioni sulle differenze tra le versioni, vedere l'articolo Durable Functions Versions.


Modello di lavoro isolato
[Function("E1_HelloSequence")]
public static async Task<List<string>> Run(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var outputs = new List<string>();

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

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

[Function("E1_SayHello")]
public static string SayHello([ActivityTrigger] string name)
{
    return $"Hello {name}!";
}

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

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

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

[FunctionName("E1_SayHello")]
public static string SayHello([ActivityTrigger] string name)
{
    return $"Hello {name}!";
}

L'esempio seguente illustra la condivisione dello stato di avanzamento usando le API client durable task SDK:

using System.Threading.Tasks;
using Microsoft.DurableTask;

public class HelloCities : TaskOrchestrator<object?, string>
{
    public override async Task<string> RunAsync(TaskOrchestrationContext context, object? input)
    {
        string result = "";

        result += await context.CallActivityAsync<string>("SayHello", "Tokyo") + ", ";
        context.SetCustomStatus("Tokyo");

        result += await context.CallActivityAsync<string>("SayHello", "London") + ", ";
        context.SetCustomStatus("London");

        result += await context.CallActivityAsync<string>("SayHello", "Seattle");
        context.SetCustomStatus("Seattle");

        return result;
    }
}

Il client può eseguire il polling dei metadati di orchestrazione e attendere che il CustomStatus campo sia impostato su "London":

using System.Threading.Tasks;
using Microsoft.DurableTask.Client;

string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("HelloCities");

OrchestrationMetadata metadata = await client.WaitForInstanceStartAsync(instanceId, getInputsAndOutputs: true);
while (metadata.SerializedCustomStatus is null || metadata.ReadCustomStatusAs<string>() != "London")
{
    await Task.Delay(200);
    metadata = await client.GetInstanceAsync(instanceId, getInputsAndOutputs: true) ?? metadata;
}

Il codice client seguente esegue il polling dello stato di orchestrazione e attende fino a quando CustomStatus non viene impostato su "London" prima di restituire una risposta:


Modello di lavoro isolato
[Function("HttpStart")]
public static async Task<HttpResponseData> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orchestrators/{functionName}")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    string functionName,
    FunctionContext executionContext)
{
    ILogger log = executionContext.GetLogger("HttpStart");
    string? eventData = await req.ReadFromJsonAsync<string>();
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(functionName, eventData);

    log.LogInformation("Started orchestration with ID = '{InstanceId}'.", instanceId);

    OrchestrationMetadata? metadata = await client.GetInstanceAsync(instanceId, getInputsAndOutputs: true);
    while (metadata is null || metadata.ReadCustomStatusAs<string>() != "London")
    {
        await Task.Delay(200);
        metadata = await client.GetInstanceAsync(instanceId, getInputsAndOutputs: true);
    }

    HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
    await response.WriteAsJsonAsync(metadata);
    return response;
}

Modello in-process
[FunctionName("HttpStart")]
public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}")] HttpRequestMessage req,
    [DurableClient] IDurableOrchestrationClient starter,
    string functionName,
    ILogger log)
{
    // Function input comes from the request content.
    dynamic eventData = await req.Content.ReadAsAsync<object>();
    string instanceId = await starter.StartNewAsync(functionName, (string)eventData);

    log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

    DurableOrchestrationStatus durableOrchestrationStatus = await starter.GetStatusAsync(instanceId);
    while (durableOrchestrationStatus.CustomStatus.ToString() != "London")
    {
        await Task.Delay(200);
        durableOrchestrationStatus = await starter.GetStatusAsync(instanceId);
    }

    HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(JsonConvert.SerializeObject(durableOrchestrationStatus))
    };

    return httpResponseMessage;
  }
}

Restituire metadati dinamici ai client

È possibile usare lo stato di orchestrazione personalizzato per restituire dati strutturati, ad esempio raccomandazioni personalizzate, ai client senza creare endpoint separati. L'agente di orchestrazione imposta lo stato personalizzato in base all'input e il client lo legge tramite l'API di stato standard. In questo modo il codice lato client rimane generico, mentre tutta la logica rimane sul lato server.


Modello di lavoro isolato
[Function("CityRecommender")]
public static void Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
    int userChoice = context.GetInput<int>();

    switch (userChoice)
    {
        case 1:
            context.SetCustomStatus(new
            {
                recommendedCities = new[] { "Tokyo", "Seattle" },
                recommendedSeasons = new[] { "Spring", "Summer" }
            });
            break;
        case 2:
            context.SetCustomStatus(new
            {
                recommendedCities = new[] { "Seattle", "London" },
                recommendedSeasons = new[] { "Summer" }
            });
            break;
        case 3:
            context.SetCustomStatus(new
            {
                recommendedCities = new[] { "Tokyo", "London" },
                recommendedSeasons = new[] { "Spring", "Summer" }
            });
            break;
    }

    // Wait for user selection and refine the recommendation
}

Modello in-process
[FunctionName("CityRecommender")]
public static void Run(
  [OrchestrationTrigger] IDurableOrchestrationContext context)
{
  int userChoice = context.GetInput<int>();

  switch (userChoice)
  {
    case 1:
    context.SetCustomStatus(new
    {
      recommendedCities = new[] {"Tokyo", "Seattle"},
      recommendedSeasons = new[] {"Spring", "Summer"}
     });
      break;
    case 2:
      context.SetCustomStatus(new
      {
                recommendedCities = new[] {"Seattle", "London"},
        recommendedSeasons = new[] {"Summer"}
      });
        break;
      case 3:
      context.SetCustomStatus(new
      {
                recommendedCities = new[] {"Tokyo", "London"},
        recommendedSeasons = new[] {"Spring", "Summer"}
      });
        break;
  }

  // Wait for user selection and refine the recommendation
}

using System.Threading.Tasks;
using Microsoft.DurableTask;

public class CityRecommender : TaskOrchestrator<int, object?>
{
    public override Task<object?> RunAsync(TaskOrchestrationContext context, int userChoice)
    {
        switch (userChoice)
        {
            case 1:
                context.SetCustomStatus(new
                {
                    recommendedCities = new[] { "Tokyo", "Seattle" },
                    recommendedSeasons = new[] { "Spring", "Summer" },
                });
                break;
            case 2:
                context.SetCustomStatus(new
                {
                    recommendedCities = new[] { "Seattle", "London" },
                    recommendedSeasons = new[] { "Summer" },
                });
                break;
            case 3:
                context.SetCustomStatus(new
                {
                    recommendedCities = new[] { "Tokyo", "London" },
                    recommendedSeasons = new[] { "Spring", "Summer" },
                });
                break;
        }

        // Wait for user selection and refine the recommendation
        return Task.FromResult<object?>(null);
    }
}

Fornire dati interattivi ai client

In questo modello, l'orchestratore espone informazioni sensibili al fattore tempo, ad esempio uno sconto, un URL di prenotazione e un timeout, tramite lo stato personalizzato, poi si interrompe e attende un evento esterno. Un client legge lo stato personalizzato per visualizzare l'offerta e invia l'evento di conferma all'orchestratore quando l'utente agisce.


Modello di lavoro isolato
[Function("ReserveTicket")]
public static async Task<bool> Run(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    string userId = context.GetInput<string>();

    int discount = await context.CallActivityAsync<int>("CalculateDiscount", userId);

    context.SetCustomStatus(new
    {
        discount,
        discountTimeout = 60,
        bookingUrl = "https://www.myawesomebookingweb.com",
    });

    bool isBookingConfirmed = await context.WaitForExternalEvent<bool>("BookingConfirmed");

    context.SetCustomStatus(isBookingConfirmed
        ? new { message = "Thank you for confirming your booking." }
        : new { message = "The booking was not confirmed on time. Please try again." });

    return isBookingConfirmed;
}

Modello in-process
[FunctionName("ReserveTicket")]
public static async Task<bool> Run(
  [OrchestrationTrigger] IDurableOrchestrationContext context)
{
  string userId = context.GetInput<string>();

  int discount = await context.CallActivityAsync<int>("CalculateDiscount", userId);

  context.SetCustomStatus(new
  {
    discount = discount,
    discountTimeout = 60,
    bookingUrl = "https://www.myawesomebookingweb.com",
  });

  bool isBookingConfirmed = await context.WaitForExternalEvent<bool>("BookingConfirmed");

  context.SetCustomStatus(isBookingConfirmed
    ? new {message = "Thank you for confirming your booking."}
    : new {message = "The booking was not confirmed on time. Please try again."});

  return isBookingConfirmed;
}

using System.Threading.Tasks;
using Microsoft.DurableTask;

public class ReserveTicket : TaskOrchestrator<string, bool>
{
    public override async Task<bool> RunAsync(TaskOrchestrationContext context, string userId)
    {
        int discount = await context.CallActivityAsync<int>("CalculateDiscount", userId);

        context.SetCustomStatus(new
        {
            discount,
            discountTimeout = 60,
            bookingUrl = "https://www.myawesomebookingweb.com",
        });

        bool isBookingConfirmed = await context.WaitForExternalEvent<bool>("BookingConfirmed");
        context.SetCustomStatus(isBookingConfirmed
            ? new { message = "Thank you for confirming your booking." }
            : new { message = "The booking was not confirmed on time. Please try again." });

        return isBookingConfirmed;
    }
}

Eseguire query sullo stato di orchestrazione personalizzato

Negli esempi precedenti viene illustrato come impostare uno stato personalizzato dal codice dell'orchestratore. Questa sezione è incentrata sul modo in cui i client esterni leggono tale valore.

Dopo che un agente di orchestrazione chiama SetCustomStatus, i client esterni possono eseguire query sul valore tramite l'API HTTP predefinita Durable Functions. Per esempio:

GET /runtime/webhooks/durabletask/instances/instance123

La risposta include il customStatus campo insieme ai metadati di runtime.

{
  "runtimeStatus": "Running",
  "input": null,
  "customStatus": { "nextActions": ["A", "B", "C"], "foo": 2 },
  "output": null,
  "createdTime": "2019-10-06T18:30:24Z",
  "lastUpdatedTime": "2019-10-06T19:40:30Z"
}

È anche possibile eseguire query sullo stato personalizzato a livello di codice usando l'SDK client di orchestrazione. Per informazioni di riferimento complete, vedere Istanze di query.

Gli SDK per attività durevoli non forniscono un endpoint di stato HTTP predefinito. Invece, consulta lo stato personalizzato in modo programmativo usando le API di metadati dell'istanza di orchestrazione dell'SDK.

using Microsoft.DurableTask.Client;

OrchestrationMetadata? metadata = await client.GetInstanceAsync(instanceId, getInputsAndOutputs: true);
string? customStatusJson = metadata?.SerializedCustomStatus;

Avvertimento

Il payload di stato personalizzato è limitato a 16 KB di testo JSON UTF-16.

Passaggi successivi