Orquestrações Duráveis

As Durable Functions são uma extensão do Azure Functions. É possível usar uma função funções de orquestrador para orquestrar a execução de outras Durable Functions em um aplicativo de funções. As funções de orquestrador têm as seguintes características:

  • As funções de orquestrador definem fluxos de trabalho de função que usam o código do procedimento. Não são necessários esquemas declarativos ou designers.
  • As funções de orquestrador podem chamar outras Durable Functions de maneira síncrona e assíncrona. A saída das funções chamadas pode ser salva em variáveis locais com confiança.
  • As funções de orquestrador são duráveis e confiáveis. O progresso da execução é marcado automaticamente com um ponto de verificação quando a função “aguarda” ou “gera”. O estado local nunca é perdido quando o processo é reciclado ou a VM é reiniciada.
  • As funções de orquestrador podem ser de longa duração. O ciclo de vida total de uma instância de orquestração pode ser de segundos, dias, meses ou interminável.

Este artigo oferece uma visão geral das funções de orquestrador e como elas podem ajudar a resolver vários desafios de desenvolvimento do aplicativo. Se você ainda não está familiarizado com os tipos de funções disponíveis em um aplicativo Durable Functions, leia primeiro o artigo sobre tipos de Funções Duráveis.

Identidade de orquestração

Cada instância de uma orquestração tem um identificador de instância (também conhecido como ID de uma instância). Por padrão, cada ID de instância é um GUID gerado automaticamente. No entanto, as IDs de instância também podem ser qualquer valor de cadeia de caracteres gerado pelo usuário. Cada ID de instância de orquestração deve ser exclusiva dentro de um hub de tarefas.

A seguir, há algumas regras sobre IDs de instância:

  • As IDs de instância devem ter entre 1 e 100 caracteres.
  • As IDs de instância não devem começar com @.
  • As IDs de instância não devem conter /, \, # ou ? caracteres.
  • As IDs de instância não devem conter caracteres de controle.

Observação

Geralmente, é recomendável usar IDs de instância geradas automaticamente sempre que possível. As IDs de instância geradas pelo usuário são destinadas a cenários em que há um mapeamento de um para um entre uma instância de orquestração e alguma entidade externa específica do aplicativo, como uma ordem de compra ou um documento.

Além disso, a imposição real das regras de restrição de caractere pode variar dependendo do provedor de armazenamento que está sendo usado pelo aplicativo. Para garantir o comportamento e a compatibilidade corretos, é altamente recomendável que você siga as regras de ID de instância listadas anteriormente.

A ID da instância de uma orquestração é um parâmetro obrigatório para a maioria das operações de gerenciamento de instância. Elas também são importantes para diagnóstico, como pesquisa por meio de dados de acompanhamento de orquestração no Application Insights para fins de solução de problemas ou de análise. Por esse motivo, é recomendável salvar as IDs de instância geradas em alguma localização externa (por exemplo, um banco de dados ou em logs de aplicativo), em que elas podem ser facilmente consultadas mais tarde.

Confiabilidade

As funções de orquestrador mantêm com confiança seu estado de execução usando o padrão de design de fornecimento de eventos. Em vez de armazenar diretamente o estado atual de uma orquestração, a Estrutura de Tarefas Duráveis usa um armazenamento somente de acréscimo para registrar a série completa de ações executadas pela orquestração de função. Um armazenamento somente de acréscimo tem muitos benefícios em comparação com o "despejo" do estado de runtime completo. Os benefícios incluem maior desempenho, escalabilidade e capacidade de resposta. Você também obtém consistência eventual para dados transacionais e trilhas e histórico de auditoria completos. As trilhas de auditoria dão suporte a ações de compensação confiáveis.

As Durable Functions usam o fornecimento de eventos de forma transparente. Nos bastidores, o operador await (C#) ou yield (JavaScript/Python) de uma função de orquestrador leva o controle do thread do orquestrador de volta para o dispatcher da Estrutura de Tarefas Duráveis. No caso do Java, não existe palavra-chave de linguagem especial. Em vez disso, chamar .await() em uma tarefa suspenderá o controle de volta ao dispatcher por meio de um Throwable personalizado. O dispatcher, em seguida, confirma novas ações agendadas pela função orquestradora (como chamar uma ou mais funções filho ou agendar um temporizador durável) no armazenamento. A ação de confirmação transparente atualiza o histórico de execuções da instância de orquestração acrescentando todos os novos eventos ao armazenamento, assim como um log somente de acréscimo. De maneira semelhante, a ação de confirmação cria mensagens no armazenamento para agendar o trabalho real. Neste ponto, a função orquestradora pode ser descarregada da memória. Por padrão, o Durable Functions usa o Armazenamento do Azure como o armazenamento de estado do runtime, mas outros provedores de armazenamento também têm suporte.

Quando uma função de orquestração recebe mais trabalho a fazer (por exemplo, uma mensagem de resposta é recebida ou um temporizador durável expira), o orquestrador é ativado e executa mais uma vez a função inteira, desde o início, para recompilar o estado local. Durante a reprodução, se o código tentar chamar uma função (ou executar qualquer outro trabalho assíncrono), a Estrutura de Tarefas Duráveis consultará o histórico de execução da orquestração atual. Se ele detectar que a função de atividade já foi executada e gerou um resultado, ele reproduzirá o resultado da função e o código do orquestrador continuará em execução. A reprodução continua até que o código da função seja concluído ou até que tenha agendado um novo trabalho assíncrono.

Observação

Para que o padrão de reprodução funcione corretamente e de forma confiável, o código de função funções de orquestrador deve ser determinístico. O código do orquestrador não determinístico pode resultar em erros de runtime ou em outro comportamento inesperado. Para obter mais informações sobre restrições de código para funções de orquestrador, confira a documentação restrições de código de função funções de orquestrador.

Observação

Se uma função de orquestrador emitir mensagens de log, o comportamento da reprodução poderá causar mensagens de erro duplicadas a serem emitidas. Confira o tópico Log para saber mais sobre por que esse comportamento ocorre e como resolvê-lo.

Histórico de orquestração

O comportamento de fornecimento de eventos da Estrutura de Tarefas Duráveis está intimamente acoplado ao código da função funções de orquestrador que você escreve. Suponha que você tenha uma função de orquestrador de encadeamento de atividades, como a seguinte função de orquestrador:

Observação

A versão 4 do modelo de programação Node.js para o Azure Functions está em disponibilidade geral. O novo modelo v4 é projetado para oferecer uma experiência mais flexível e intuitiva para desenvolvedores de JavaScript e TypeScript. Saiba mais sobre as diferenças entre v3 e v4 na guia de migração.

Nos trechos de código a seguir, o JavaScript (PM4) denota o modelo de programação V4, a nova experiência.

[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;
}

Sempre que uma atividade de função é agendada, o Durable Task Framework verifica o estado da execução da função em algum back-end de armazenamento durável (por padrão, no Armazenamento de Tabelas do Azure). Esse estado é o que chamamos de histórico de orquestração.

Tabela de histórico

De modo geral, o Framework de Tarefa Durável faz o seguinte em cada ponto de verificação:

  1. Salva o histórico de execuções no armazenamento durável.
  2. Enfileira mensagens para as funções que o orquestrador quer invocar.
  3. Enfileira mensagens para o próprio orquestrador, por exemplo, mensagens de temporizador durável.

Quando o ponto de verificação for concluído, a função de orquestrador estará livre para ser removida da memória até que tenha mais trabalho a fazer.

Observação

O Armazenamento do Azure não oferece nenhuma garantia transacional entre salvar dados em filas e no armazenamento de tabelas. Para lidar com falhas, o provedor do Armazenamento do Azure do Durable Functions usa padrões de consistência eventual. Esses padrões garantem que nenhum dado seja perdido se houver uma falha ou perda de conectividade no meio de um ponto de verificação. Provedores de armazenamento alternativos, como o provedor de armazenamento MSSQL do Durable Functions, podem fornecer garantias de consistência mais fortes.

Após a conclusão, o histórico da função mostrado anteriormente se parece com a seguinte tabela no Armazenamento de Tabelas do Azure (abreviado para fins de ilustração):

PartitionKey (InstanceId) EventType Timestamp Entrada Nome Result Status
eaee885b ExecutionStarted 2021-05-05T18:45:28.852Z nulo HelloCities
eaee885b OrchestratorStarted 2021-05-05T18:45:32.362Z
eaee885b TaskScheduled 2021-05-05T18:45:32.67Z SayHello
eaee885b OrchestratorCompleted 2021-05-05T18:45:32.67Z
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!""]" Concluído

Algumas observações sobre os valores das colunas:

  • PartitionKey: Contém a ID de instância da orquestração.
  • EventType: Representa o tipo do evento. Você pode encontrar descrições detalhadas de todos os tipos de eventos de histórico aqui.
  • Timestamp: O carimbo de data/hora, em UTC, do evento do histórico.
  • Name: O nome da função que foi invocada.
  • Entrada: A entrada da função formatada em JSON.
  • Result: A saída da função, ou seja, o valor retornado.

Aviso

Embora ela seja útil como uma ferramenta de depuração, não dependa desta tabela. Ela pode mudar à medida que a extensão de Funções Duráveis evoluir.

Sempre que a função é retomada depois de aguardar a conclusão de uma tarefa, o Durable Task Framework executa novamente a função de orquestrador do zero. A cada nova execução, ele consulta o histórico de execução para determinar se a tarefa assíncrona atual foi concluída. Se o histórico de execução mostrar que a tarefa já foi concluída, a estrutura reproduzirá a saída dessa tarefa e passará para a próxima tarefa. Esse processo continua até que todo o histórico de execução tenha sido reproduzido. Depois que o histórico de execução atual tiver sido reproduzido, as variáveis locais terão sido restauradas para seus valores anteriores.

Recursos e padrões

As seções a seguir descrevem os recursos e padrões de funções de orquestrador.

Subtipo de orquestrações

As funções de orquestrador podem chamar funções de atividade, mas também outras funções de orquestrador. Por exemplo, você pode criar uma orquestração maior usando uma biblioteca de funções de orquestrador. Ou pode executar várias instâncias de uma função de orquestrador em paralelo.

Para obter mais informações e exemplos, confira o artigo Suborquestrações.

Temporizadores duráveis

As orquestrações podem agendar temporizadores duráveis para implementar atrasos ou configurar a manipulação de tempo limite em ações assíncronas. Use temporizadores duráveis em funções de orquestrador em vez de APIs "suspender" nativas da linguagem.

Para obter mais informações e exemplos, confira o artigo Temporizadores duráveis.

Eventos externos

As funções de orquestrador podem aguardar os eventos externos atualizarem uma instância de orquestração. O recurso Durable Functions geralmente é útil para manipular uma interação humana ou outros retornos de chamada externos.

Para obter mais informações e exemplos, confira o artigo Eventos externos.

Tratamento de erros

As funções de orquestrador podem usar os recursos de tratamento de erro da linguagem de programação. No código de orquestração, há suporte para padrões existentes, como try/catch.

As funções de orquestrador também podem adicionar políticas de repetição à atividade ou às funções de suborquestrador que elas chamam. Se uma atividade ou função de suborquestrador falhar com uma exceção, a política de repetição especificada poderá atrasar automaticamente e repetir a execução até um número especificado de vezes.

Observação

Se houver uma exceção sem tratamento em uma função funções de orquestrador, a instância de orquestração será concluída em um estado Failed. Uma instância de orquestração não poderá ser repetida após ter falhado.

Para obter mais informações e exemplos, confira o artigo Tratamento de erro.

Seções críticas (Durable Functions 2.x, atualmente somente no .NET)

As instâncias de orquestração têm thread único para que não seja preciso se preocupar com condições de corrida dentro de uma orquestração. No entanto, as condição de corrida são possíveis quando as orquestrações interagem com sistemas externos. Para atenuar as condições de corrida ao interagir com sistemas externos, as funções de orquestrador podem definir seções críticas usando um método LockAsync no .NET.

O exemplo de código a seguir mostra uma função funções de orquestrador que define uma seção crítica. Ele entra na seção crítica usando o método LockAsync. Esse método requer a passagem de uma ou mais referências a uma Entidade Durável, que gerencia de maneira durável o estado de bloqueio. Somente uma única instância dessa orquestração pode executar o código na seção crítica por vez.

[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
    }
}

O LockAsync adquire os bloqueios duráveis e retorna um IDisposable que encerra a seção crítica quando descartado. Esse resultado IDisposable pode ser usado junto com um bloco using para obter uma representação sintática da seção crítica. Quando uma função funções de orquestrador entra em uma seção crítica, apenas uma instância pode executar esse bloco de código. Outras instâncias que tentarem entrar na seção crítica serão bloqueadas até que a instância anterior saia da seção crítica.

O recurso de seção crítica também é útil para coordenar alterações em entidades duráveis. Para obter informações sobre seções críticas, confira o tópico “Coordenação de entidade” de entidades duráveis.

Observação

As seções críticas estão disponíveis nas Durable Functions 2.0. No momento, apenas as orquestrações do .NET em processo implementam esse recurso. As entidades e as seções críticas ainda não estão disponíveis nas Durable Functions para o trabalho isolado do .NET.

Chamar pontos de extremidade HTTP (Durable Functions 2.x)

As funções de orquestrador não têm permissão para realizar E/S conforme descrito nas restrições de código da função funções de orquestrador. A alternativa típica para essa limitação é encapsular o código que precisa realizar E/S em uma função de atividade. As orquestrações que interagem com sistemas externos frequentemente usam funções de atividade para realizar chamadas HTTP e retornar o resultado à orquestração.

Para simplificar esse padrão comum, as funções de orquestrador podem usar o método CallHttpAsync para invocar APIs HTTP diretamente.

[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
    }
}

Além de dar suporte a padrões básicos de solicitação/resposta, o método dá suporte ao tratamento automático de padrões de sondagem HTTP 202 assíncronos comuns e à autenticação com serviços externos usando identidades gerenciadas.

Para obter mais informações e exemplos detalhados, confira o artigo Recursos HTTP.

Observação

Chamar pontos de extremidade HTTP diretamente de funções de orquestrador está disponível nas Durable Functions 2.0 e superiores.

Passando vários parâmetros

Não é possível passar vários parâmetros para uma função de atividade diretamente. A recomendação é transmitir uma matriz de objetos ou objetos de composição.

No .NET, use também objetos ValueTuple. O seguinte exemplo usa os novos recursos de ValueTuple adicionados com 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"
        }
    };
}

Próximas etapas