Controle de Versão nas Funções Duráveis (Azure Functions)
É inevitável que funções sejam adicionadas, removidas e alteradas ao longo do tempo de vida de um aplicativo. As Funções Duráveis permitem encadear funções de maneiras que não eram possíveis anteriormente e esse encadeamento afeta a forma como você pode tratar o controle de versão.
Como lidar com as alterações que causam interrupção
Há vários exemplos de alterações que causam interrupção a serem considerados. Este artigo discute os mais comuns. O tema principal por trás delas é que tanto orquestrações de função novas quando as já existentes são afetadas por alterações no código da função.
Alterando assinaturas da função de atividade ou entidade
Uma alteração de assinatura é uma alteração no nome, na entrada ou na saída de uma função. Se esse tipo de alteração for feita em uma função de atividade ou entidade, ela poderá interromper a função de orquestrador que depende dela. Isso é especialmente verdadeiro para linguagens fortemente tipados. Se atualizar a função de orquestrador para acomodar essa alteração, você poderá interromper instâncias existentes em curso.
Por exemplo, suponha que você tenha a seguinte função de orquestrador.
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
Essa função simples obtém os resultados de Foo e os transmite para Bar. Digamos que precisemos alterar o valor retornado de Foo de um Booleano para uma String para dar suporte a uma variedade maior de valores de resultado. O resultado será semelhante a este:
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
string result = await context.CallActivityAsync<string>("Foo");
await context.CallActivityAsync("Bar", result);
}
Essa alteração funciona bem para todas as novas instâncias da função de orquestrador, mas poderá interromper todas as instâncias de versões piloto. Por exemplo, considere o caso em que uma instância de orquestração chama uma função com o nome Foo
, obtém de volta um valor booliano e, em seguida, faz uma verificação pontual. Se a alteração da assinatura for implantada nesse ponto, a instância que passou pela verificação falhará imediatamente quando for retomada e reproduzir a chamada para Foo
. Essa falha ocorre porque o resultado na tabela de histórico é um valor Booliano, mas o novo código tenta desserializá-lo para um valor de cadeia de caracteres, resultando em um comportamento inesperado ou uma exceção de runtime para linguagens fortemente tipadas.
Este exemplo é apenas uma das várias maneiras que uma alteração de assinatura de função pode interromper as instâncias existentes. De modo geral, se um orquestrador precisar alterar a forma como chama uma função, provavelmente a alteração será um problema.
Alterando a lógica do orquestrador
O outro tipo de problema de controle de versão é consequente da alteração do código de função do orquestrador de uma forma que altera o caminho de execução para as instâncias em andamento.
Considere a seguinte função de orquestrador:
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
Suponhamos que você deseja fazer uma alteração para adicionar uma nova chamada de função entre as duas chamadas de função existentes.
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
if (result)
{
await context.CallActivityAsync("SendNotification");
}
await context.CallActivityAsync("Bar", result);
}
Essa alteração adiciona uma nova chamada de função a SendNotification, entre Foo e Bar. Não há nenhuma alteração de assinatura. O problema surge quando uma instância existente é retomada da chamada para Bar. Durante a reprodução, se a chamada original para Foo retornar true
, a reprodução do orquestrador chamará SendNotification, que não está em seu histórico de execução. O runtime detecta essa inconsistência e gera um erro de orquestração não determinístico porque encontrou uma chamada para SendNotification quando esperava ver uma chamada para Bar. O mesmo tipo de problema pode ocorrer ao adicionar chamadas à API a outras operações duráveis, como criar temporizadores duráveis, aguardar eventos externos, chamar suborquestrações etc.
Estratégias de mitigação
Veja algumas das estratégias para lidar com desafios de controle de versão:
- Não fazer nada (não recomendado)
- Parar todas as instâncias em curso
- Implantações lado a lado
Não fazer nada
A abordagem simples para o controle de versão é não fazer nada e deixar que as instâncias de orquestração em andamento falhem. Dependendo do tipo de alteração, podem ocorrer os seguintes tipos de falhas.
- As orquestrações podem falhar com um erro de orquestração não determinístico.
- As orquestrações podem ficar travadas indefinidamente, relatando um status
Running
. - Se uma função for removida, qualquer função que tentar chamá-la poderá falhar com erro.
- Se uma função for removida após ter sido agendada para execução, o aplicativo pode apresentar falhas de tempo de execução de baixo nível no mecanismo Durable Task Framework, resultando possivelmente em grave degradação do desempenho.
Devido a essas possíveis falhas, a estratégia de "não fazer nada" não é recomendada.
Parar todas as instâncias em curso
Outra opção é parar todas as instâncias em curso. Se você estiver usando o provedor de Armazenamento do Microsoft Azure padrão para o Durable Functions, a interrupção de todas as instâncias pode ser feita limpando o conteúdo das filas internas control-queue e workitem-queue. Como alternativa, você pode parar o aplicativo de funções, excluir essas filas e reiniciar o aplicativo novamente. As filas serão recriadas automaticamente depois que o aplicativo for reiniciado. As instâncias de orquestração anteriores podem permanecer no estado "em execução" indefinidamente, mas elas não obstruirão os logs com mensagens de falha ou causarão danos ao aplicativo. Isso é ideal no desenvolvimento rápido de protótipos, incluindo desenvolvimento local.
Observação
Essa abordagem requer acesso direto aos recursos de armazenamento subjacentes e não é apropriada para todos os provedores de armazenamento suportados pelo Durable Functions.
Implantações lado a lado
A maneira mais infalível de garantir que alterações que causam interrupção sejam implantadas com segurança é implantá-las lado a lado com as versões mais antigas. Isso pode ser feito usando qualquer uma das seguintes técnicas:
- Implante todas as atualizações como funções totalmente novas, deixando as funções existentes como estão. Isso geralmente não é recomendado devido à complexidade envolvida na atualização recursiva dos chamadores das novas versões de função.
- Implantar todas as atualizações como um novo aplicativo de funções com uma conta de armazenamento diferente.
- Implante uma nova cópia do aplicativo de funções com a mesma conta de armazenamento, mas com um nome de hub de tarefas atualizado. Isso resulta na criação de novos artefatos de armazenamento que podem ser usados pela nova versão do aplicativo. A versão antiga do aplicativo continuará a ser executada usando o conjunto anterior de artefatos de armazenamento.
A implantação lado a lado é a técnica recomendada para implantar novas versões dos aplicativos de funções.
Observação
Essas diretrizes para a estratégia de implantação lado a lado usam termos específicos do Armazenamento do Microsoft Azure, mas se aplica geralmente a todos os provedores de armazenamento Durable Functions com suporte.
Slots de implantação
Ao fazer implantações lado a lado no Azure Functions ou no Serviço de Aplicativo do Azure, recomendamos que você implante a nova versão do aplicativo de funções em um novo slot de implantação. Os slots de implantação permitem que você execute várias cópias de seu aplicativo de funções lado a lado, com apenas uma delas como o slot de produção ativo. Quando você estiver pronto para expor a nova lógica de orquestração para sua infraestrutura existente, isso pode ser tão simples quanto colocar a nova versão no slot de produção.
Observação
Essa estratégia funciona melhor quando você usa gatilhos de webhook e HTTP para funções de orquestrador. Para gatilhos não HTTP, como filas ou Hubs de Eventos, a definição do gatilho deve derivar de uma configuração de aplicativo que seja atualizada como parte da operação de troca.