Restrições de código de função do orquestrador

A Durable Functions é uma extensão do Azure Functions que permite compilar aplicativos com estado. É possível usar uma função do orquestrador para orquestrar a execução de outra Durable Functions em um aplicativo de funções. As funções de orquestrador são confiáveis, com estado e potencialmente demoradas.

Restrições de código do orquestrador

As funções de orquestrador usam o fornecimento de eventos para garantir a execução confiável e para manter o estado da variável local. O comportamento de reprodução do código do orquestrador cria restrições quanto ao tipo de código que você pode escrever em uma função do orquestrador. Por exemplo, as funções do orquestrador devem ser determinísticas: uma função do orquestrador será repetida várias vezes e deverá produzir o mesmo resultado a cada vez.

Usando APIs determinísticas

Esta seção fornece algumas diretrizes simples que ajudam a garantir que seu código seja determinístico.

As funções do orquestrador podem chamar qualquer API em seus idiomas de destino. No entanto, é importante que as funções do orquestrador chamem apenas APIs determinísticas. Uma API determinística é uma API que sempre retorna o mesmo valor devido à mesma entrada, não importa quando ou com que frequência ela é chamada.

As seções a seguir fornecem diretrizes sobre APIs e padrões que você deve evitar porque eles não são determinísticos. Essas restrições se aplicam somente a funções do orquestrador. Outros tipos de função não têm essas restrições.

Observação

Vários tipos de restrições de código são descritos abaixo. Infelizmente, essa lista não é abrangente e alguns casos de uso podem não ser abordados. A coisa mais importante a considerar ao escrever código de orquestrador é se uma API que você está usando é determinística. Após se sentir confortável em pensar dessa forma, é fácil entender quais APIs são seguras de usar e quais não são, sem a necessidade de se referir a essa lista documentada.

Datas e horas

As APIs que retornam a data ou hora atual não são determinísticas e nunca devem ser usadas em funções de orquestrador. Isso ocorre porque cada reprodução de função do orquestrador produzirá um valor diferente. Em vez disso, você deve usar a API do Durable Functions equivalente para obter a data ou hora atual, que permanece consistente nas reproduções.

Não use DateTime.Now, DateTime.UtcNow ou APIs equivalentes para obter a hora atual. Classes como Stopwatch também devem ser evitadas. Para funções de orquestrador no processo do .NET, use a propriedade IDurableOrchestrationContext.CurrentUtcDateTime para obter a hora atual. Para funções de orquestrador isoladas do .NET, use a propriedade TaskOrchestrationContext.CurrentDateTimeUtc para obter a hora atual.

DateTime startTime = context.CurrentUtcDateTime;
// do some work
TimeSpan totalTime = context.CurrentUtcDateTime.Subtract(startTime);

GUIDs e UUIDs

As APIs que retornam um GUID aleatório ou UUID são não determinísticas porque o valor gerado é diferente para cada repetição. Dependendo de qual idioma você usa, uma API interna para gerar GUIDs determinísticos ou UUIDs pode estar disponível. Caso contrário, use uma função de atividade para retornar um GUID ou UUID gerado aleatoriamente.

Não use APIs como Guid.NewGuid() para gerar GUIDs aleatórios. Em vez disso, use a API NewGuid() do objeto de contexto para gerar um GUID aleatório seguro para reprodução de orquestrador.

Guid randomGuid = context.NewGuid();

Observação

GUIDs gerados com APIs de contexto de orquestração são UUIDs tipo 5.

Números aleatórios

Use uma função de atividade para retornar números aleatórios para uma função de orquestração. Os valores retornados das funções de atividade são sempre seguros para reprodução porque são salvos no histórico de orquestração.

Como alternativa, um gerador de número aleatório com um valor de semente fixa pode ser usado diretamente em uma função de orquestrador. Essa abordagem é segura desde que a mesma sequência de números seja gerada para cada reprodução de orquestração.

Associações

Uma função do orquestrador não deve usar nenhuma associação, incluindo as associações de cliente de orquestração e cliente de entidade. Sempre use associações de entrada e saída de dentro de uma função de cliente ou atividade. Isso é importante porque as funções de orquestrador podem ser reproduzidas várias vezes, causando E/S não determinística e duplicada com sistemas externos.

Variáveis estáticas

Evite usar variáveis estáticas em funções de orquestrador porque seus valores podem mudar ao longo do tempo, resultando em um comportamento de tempo de execução não determinístico. Em vez disso, use constantes ou limite o uso de variáveis estáticas para funções de atividade.

Observação

Mesmo fora das funções de orquestrador, o uso de variáveis estáticas no Azure Functions pode ser problemático por diversas razões, pois não há garantia de que o estado estático persistirá em várias execuções de função. Variáveis estáticas devem ser evitadas, exceto em casos de uso muito específicos, como o cache na memória com melhor esforço em funções de atividade ou entidade.

Variáveis de ambiente

Não use variáveis de ambiente em funções do orquestrador. Seus valores podem mudar ao longo do tempo, resultando em um comportamento de tempo de execução não determinístico. Se uma função de orquestrador precisar de configuração definida em uma variável de ambiente, você deve passar o valor de configuração para a função de orquestrador como uma entrada ou como o valor retornado de uma função de atividade.

Rede e HTTP

Use funções de atividade para fazer chamadas de rede de saída. Se você precisar fazer uma chamada HTTP de sua função de orquestrador, também poderá usar as APIs de HTTP duráveis.

APIs de bloqueio de thread

As APIs de bloqueio como “suspender” podem causar problemas de desempenho e escala para funções de orquestrador e devem ser evitadas. No plano de consumo do Azure Functions, elas podem até mesmo resultar em encargos de tempo de execução desnecessários. Use alternativas para bloquear APIs quando elas estiverem disponíveis. Por exemplo, use temporizadores duráveis para criar atrasos que são seguros para reprodução e não contam para o tempo de execução de uma função de orquestrador.

APIs assíncronas

O código do Orchestrator nunca deve iniciar uma operação assíncrona, exceto aquelas definidas pelo objeto de contexto do gatilho de orquestração. Por exemplo, nunca use Task.Run, Task.Delay e HttpClient.SendAsync no .NET ou setTimeoutsetInterval no JavaScript. Uma função de orquestrador somente deve agendar o trabalho assíncrono usando APIs duráveis do SDK, como funções de atividade de agendamento. Quaisquer outros tipos de invocações assíncronas devem ser feitas dentro das funções de atividade.

Funções JavaScript assíncronas

Sempre declare funções do orquestrador JavaScript como funções de gerador síncrono. Você não deve declarar funções do orquestrador JavaScript como async porque o tempo de execução do Node.js não garante que as funções assíncronas sejam determinísticas.

Corrotinas do Python

Você não deve declarar funções do orquestrador do Python como corrotinas. Em outras palavras, nunca declare funções de orquestrador do Python com a palavra-chave async, porque a semântica de corrotina não se alinha com o modelo de reprodução do Durable Functions. Você sempre deve declarar funções do orquestrador do Python como geradores, o que significa que você deve esperar que a API context use yield em vez de await.

APIs de thread .NET

A Durable Task Framework executa o código do orquestrador em um único thread e não pode interagir com outros threads. Ao executar continuações assíncronas em um thread de pool de trabalho a execução de uma orquestração pode resultar em execução não determinística ou deadlocks. Por esse motivo, as funções do orquestrador quase nunca devem usar APIs de thread. Por exemplo, nunca use ConfigureAwait(continueOnCapturedContext: false) em uma função de orquestrador. Isso garante que as continuações de tarefa sejam executadas no original SynchronizationContext da função do orquestrador.

Observação

O Durable Task Framework tenta detectar o uso acidental de threads não orquestradores em funções de orquestrador. Se encontrar uma violação, a estrutura lançará uma exceção NonDeterministicOrchestrationException. No entanto, esse comportamento de detecção não capturará todas as violações e você não deverá depender dela.

Controle de versão

Uma orquestração durável pode ser executada continuamente por dias, meses, anos ou até mesmo eternamente. Quaisquer atualizações de código feitas nos aplicativos da Durable Functions que afetam orquestrações não concluídas podem interromper o comportamento de reprodução das orquestrações. É por isso que é importante planejar com cuidado ao fazer atualizações no código. Para obter uma descrição mais detalhada de como fazer a versão de seu código, confira o artigo de controle de versão.

Tarefas duráveis

Observação

Esta seção descreve os detalhes de implementação interna do Framework de Tarefa Durável. Você pode usar as funções duráveis sem conhecer essas informações. Elas destinam-se somente a ajudá-lo a entender o comportamento de reprodução.

Ocasionalmente, tarefas que podem ser esperadas com segurança em funções do orquestrador são ocasionalmente chamadas de tarefas duráveis. O Framework de Tarefa Durável cria e gerencia essas tarefas. Os exemplos são as tarefas retornadas por CallActivityAsync, WaitForExternalEvent e CreateTimer nas funções do orquestrador do .NET.

Essas tarefas duráveis são gerenciadas internamente por uma lista de objetos TaskCompletionSource no .NET. Durante a reprodução, essas tarefas são criadas como parte da execução de código do orquestrador. Elas são concluídas à medida que o dispatcher enumera os eventos de histórico correspondentes.

As tarefas são executadas de forma sincronizada usando um único thread até que todo o histórico tenha sido reproduzido. As tarefas duráveis que não são concluídas pelo fim da reprodução do histórico têm as ações apropriadas executadas. Por exemplo, uma mensagem pode ser enfileirada para chamar uma função de atividade.

A descrição desta seção de comportamento de tempo de execução deve ajudá-lo a entender por que uma função do orquestrador não pode usar await ou yield em uma tarefa não durável. Há dois motivos: o thread do dispatcher não pode aguardar a conclusão da tarefa e qualquer retorno de chamada por essa tarefa pode corromper o estado de controle da função do orquestrador. Algumas verificações de tempo de execução estão em vigor para ajudar a detectar essas violações.

Para saber mais sobre como o Framework de Tarefa Durável executa funções do orquestrador, confira o Código-fonte de Tarefa Durável no GitHub. Em particular, confira TaskOrchestrationExecutor.cs e TaskOrchestrationContext.cs.

Próximas etapas