Compartilhar via


Controle de versão de orquestração no Durable Functions (Azure Functions) – visualização pública

O controle de versão de orquestração aborda o principal desafio de implantar alterações em funções de orquestrador, mantendo o modelo de execução determinística exigido pelo Durable Functions. Sem esse recurso, alterações interruptivas na lógica do orquestrador ou assinaturas de função de atividade fariam com que as instâncias de orquestração em voo falhassem durante a reprodução, pois quebrariam o requisito de determinismo que garante a execução de orquestração confiável. Esse recurso interno fornece isolamento automático de versão com configuração mínima. Ele é independente de back-end, portanto, pode ser usado por aplicativos que aproveitam qualquer um dos provedores de armazenamento da Função Durável, incluindo o Agendador de Tarefas Duráveis.

Note

Para usuários do Agendador de Tarefas Duráveis, se você estiver usando os SDKs de Tarefa Durável em vez do Durable Functions, consulte o artigo de controle de versão dos SDKs de Tarefas Duráveis.

Terminology

Este artigo usa dois termos relacionados, mas distintos:

  • Função orquestrador (ou simplesmente "orquestrador"): refere-se ao código de função que define a lógica do fluxo de trabalho – o modelo de como um fluxo de trabalho deve ser executado.
  • Instância de orquestração (ou simplesmente "orquestração"): refere-se a uma execução específica de uma função de orquestrador, com seu próprio estado, ID de instância e entradas. Várias instâncias de orquestração podem ser executadas simultaneamente a partir da mesma função de orquestrador.

Entender essa distinção é crucial para o controle de versão de orquestração, em que o código de função do orquestrador contém lógica com reconhecimento de versão, enquanto as instâncias de orquestração são permanentemente associadas a uma versão específica quando criadas.

Como funciona

O recurso de controle de versão de orquestração opera nestes princípios principais:

  • Associação de Versão: quando uma instância de orquestração é criada, uma versão é associada a ela de forma permanente.

  • Execução sensível à versão: o código da função do orquestrador pode examinar o valor de versão associado à instância de orquestração atual e ramificar a execução conforme necessário.

  • Compatibilidade com versões anteriores: os trabalhadores que executam versões mais recentes do orquestrador podem continuar executando instâncias de orquestração criadas por versões mais antigas do orquestrador.

  • Proteção de Encaminhamento: o runtime impede automaticamente que os trabalhadores que executam versões mais antigas do orquestrador executem orquestrações iniciadas por versões mais recentes do orquestrador.

Important

O controle de versão de orquestração está atualmente em versão prévia pública.

Pré-requisitos

Antes de usar o versionamento de orquestração, verifique se você tem as versões de pacote necessárias para sua linguagem de programação.

Se você estiver usando uma linguagem non-.NET (JavaScript, Python, PowerShell ou Java) com pacotes de extensão, seu aplicativo de funções deverá referenciar o Pacote de Extensão versão 4.26.0 ou posterior. Configure o intervalo extensionBundle no host.json para que a versão mínima seja no mínimo 4.26.0, por exemplo:

{
    "version": "2.0",
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[4.26.0, 5.0.0)"
    }
}

Consulte a documentação de configuração do pacote de extensão para obter detalhes sobre como escolher e atualizar versões de pacote.

Use Microsoft.Azure.Functions.Worker.Extensions.DurableTask a versão 1.5.0 ou posterior.

Uso Básico

O caso de uso mais comum para controle de versão de orquestração é quando você precisa fazer alterações interruptivas na lógica do orquestrador, mantendo as instâncias de orquestração existentes em execução com sua versão original. Tudo o que você precisa fazer é atualizar o defaultVersion no seu host.json e modificar seu código de orquestrador para verificar a versão da orquestração e a execução do branch adequadamente. Vamos percorrer as etapas necessárias.

Note

O comportamento descrito nesta seção tem como destino as situações mais comuns e é isso que a configuração padrão fornece. No entanto, ele pode ser modificado se necessário (consulte Uso avançado para obter detalhes).

Etapa 1: configuração da defaultVersion

Para configurar a versão padrão para suas orquestrações, você precisa adicionar ou atualizar a defaultVersion definição no host.json arquivo no seu projeto do Azure Functions.

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>"
    }
  }
}

A cadeia de caracteres de versão pode seguir qualquer formato que se adapte à sua estratégia de controle de versão:

  • Versionamento de várias partes: "1.0.0", "2.1.0"
  • Numeração simples: "1", "2"
  • Baseado em data: "2025-01-01"
  • Formato personalizado: "v1.0-release"

Depois de definir a defaultVersion, todas as novas instâncias de orquestração serão permanentemente associadas a essa versão.

Regras de comparação de versão

Quando a estratégia Strict ou CurrentOrOlder é selecionada (consulte a correspondência de versão), o runtime compara a versão da instância de orquestração com o valor defaultVersion do trabalho usando as seguintes regras:

  • Versões vazias ou nulas são tratadas como iguais.
  • Uma versão vazia ou nula é considerada mais antiga do que qualquer versão definida.
  • Se ambas as versões puderem ser analisadas como System.Version, o CompareTo método será usado.
  • Caso contrário, a comparação de cadeia de caracteres que não diferencia maiúsculas de minúsculas será executada.

Etapa 2: Lógica da função Orquestrador

Para implementar a lógica com reconhecimento de versão em sua função de orquestrador, você pode usar o parâmetro de contexto passado para o orquestrador para acessar a versão da instância de orquestração atual, o que permite ramificar sua lógica de orquestrador com base na versão.

Important

Ao implementar a lógica com reconhecimento de versão, é extremamente importante preservar a lógica exata do orquestrador para versões mais antigas. Qualquer alteração na sequência, ordem ou assinatura de chamadas de atividade para versões existentes pode interromper a reprodução determinística e fazer com que as orquestrações em andamento falhem ou produzam resultados incorretos. Os caminhos de código de versão antigos devem permanecer inalterados uma vez implantados.

[Function("MyOrchestrator")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    if (context.Version == "1.0")
    {
        // Original logic for version 1.0
        ...
    }
    else if (context.Version == "2.0")
    {
        // New logic for version 2.0
        ...
    }
    ...
}

Note

A propriedade context.Version é somente leitura e reflete a versão que foi permanentemente associada à instância de orquestração quando foi criada. Não é possível modificar esse valor durante a execução da orquestração. Se você quiser especificar uma versão por meio de host.jsonmeios diferentes, poderá fazer isso ao iniciar uma instância de orquestração com as APIs do cliente de orquestração (consulte Iniciar novas orquestrações e sub-orquestrações com versões específicas).

Tip

Caso esteja apenas começando a usar o controle de versão de orquestração e já tiver orquestrações em pré-lançamento que foram criadas antes de especificar um defaultVersion, você ainda poderá adicionar a defaultVersion configuração ao seu host.json agora. Para todas as orquestrações criadas anteriormente, context.Version retorna null (ou um valor equivalente que depende da linguagem), para que você possa estruturar sua lógica de orquestrador para lidar adequadamente com as orquestrações herdadas (versão nula) e novas versões. Veja a seguir os valores dependentes de idioma para verificar o caso herdado:

  • C#: context.Version == null ou context.Version is null
  • JavaScript: context.df.version == null
  • Python: context.version is None
  • PowerShell: $null -eq $Context.Version
  • Java: context.getVersion() == null Observe também que especificar "defaultVersion": null em host.json é equivalente a não especificá-lo.

Tip

Dependendo da sua situação, você pode preferir ramificar em níveis diferentes. Você pode fazer uma alteração local exatamente onde essa alteração é necessária, como mostra o exemplo. Como alternativa, você pode ramificar em um nível mais alto, mesmo em todo o nível de implementação do orquestrador, o que introduz alguma duplicação de código, mas pode manter o fluxo de execução limpo. Cabe a você escolher a abordagem que melhor se ajusta ao seu cenário e estilo de codificação.

O que acontece após a implantação

Veja o que esperar depois de implantar sua função de orquestrador atualizada com a nova lógica de versão:

  • Coexistência de trabalho: os trabalhadores que contêm o novo código de função do orquestrador serão iniciados, enquanto alguns trabalhadores com o código antigo ainda estarão ativos.

  • Atribuição de Versão para Novas Instâncias: todas as novas orquestrações e sub-orquestrações criadas pelos novos trabalhadores obterão a versão de defaultVersion atribuída a elas.

  • Nova Compatibilidade de Trabalho: novos trabalhos poderão processar as orquestrações recém-criadas e as orquestrações existentes anteriormente, pois as alterações executadas na Etapa 2 da seção anterior garantem a compatibilidade com versões anteriores por meio da lógica de ramificação com reconhecimento de versão.

  • Antigas Restrições de Trabalho: os trabalhadores antigos poderão processar apenas as orquestrações com uma versão igual ou inferior à versão especificada em si defaultVersion em host.json, porque não é esperado que elas tenham código de orquestrador compatível com versões mais recentes. Essa restrição impede erros de execução e comportamento inesperado.

Note

O controle de versão de orquestração não influencia o ciclo de vida do trabalhador. A plataforma do Azure Functions gerencia o provisionamento e o descomissionamento de trabalho com base em regras regulares, dependendo das opções de hospedagem.

Exemplo: substituindo uma atividade na sequência

Este exemplo mostra como substituir uma atividade por outra diferente no meio de uma sequência usando o versionamento de orquestração.

Versão 1.0

configuração do host.json:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "1.0"
    }
  }
}

Função Orchestrator:

[Function("ProcessOrderOrchestrator")]
public static async Task<string> ProcessOrder(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var orderId = context.GetInput<string>();
    
    await context.CallActivityAsync("ValidateOrder", orderId);
    await context.CallActivityAsync("ProcessPayment", orderId);
    await context.CallActivityAsync("ShipOrder", orderId);
    
    return "Order processed successfully";
}

Versão 2.0 com processamento de desconto

configuração do host.json:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "2.0"
    }
  }
}

Função Orchestrator:

using DurableTask.Core.Settings;

[Function("ProcessOrderOrchestrator")]
public static async Task<string> ProcessOrder(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var orderId = context.GetInput<string>();

    await context.CallActivityAsync("ValidateOrder", orderId);

    if (VersioningSettings.CompareVersions(context.Version, "1.0") <= 0)
    {
        // Preserve original logic for existing instances
        await context.CallActivityAsync("ProcessPayment", orderId);
    }
    else // a higher version (including 2.0)
    {
        // New logic with discount processing (replaces payment processing)
        await context.CallActivityAsync("ApplyDiscount", orderId);
        await context.CallActivityAsync("ProcessPaymentWithDiscount", orderId);
    }
    
    await context.CallActivityAsync("ShipOrder", orderId);

    return "Order processed successfully";
}

Uso avançado

Para cenários de controle de versão mais sofisticados, você pode definir outras configurações para controlar como o runtime lida com correspondências e incompatibilidades de versão.

Tip

Use a configuração padrão (CurrentOrOlder com Reject) para a maioria dos cenários para habilitar implantações sem interrupção seguras, preservando o estado de orquestração durante as transições de versão. Recomendamos continuar com a configuração avançada somente se você tiver requisitos específicos que não possam ser atendidos com o comportamento padrão.

Correspondência de versão

A configuração versionMatchStrategy determina como o runtime corresponde às versões de orquestração ao carregar funções de orquestrador. Ele controla quais instâncias de orquestração um trabalhador pode processar com base na compatibilidade de versão.

Configuration

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>",
      "versionMatchStrategy": "CurrentOrOlder"
    }
  }
}

Estratégias disponíveis

  • None (não recomendado): ignore completamente a versão de orquestração. Todo o trabalho recebido é processado independentemente da versão. Essa estratégia efetivamente desabilita a verificação de versão e permite que qualquer trabalhador processe qualquer instância de orquestração.

  • Strict: processe apenas tarefas de orquestrações com a mesma versão da versão especificada pelo defaultVersion no host.json do trabalho. Essa estratégia fornece o nível mais alto de isolamento de versão, mas requer uma coordenação de implantação cuidadosa para evitar orquestrações órfãs. As consequências da incompatibilidade de versão são descritas na seção Tratamento de incompatibilidade de versão .

  • CurrentOrOlder (padrão): processar tarefas de orquestrações cuja versão é menor ou igual à versão especificada pelo defaultVersion no host.json do trabalho. Essa estratégia permite a compatibilidade com versões anteriores, permitindo que os trabalhadores mais recentes lidem com orquestrações iniciadas por versões mais antigas do orquestrador, impedindo que os trabalhadores mais velhos processem orquestrações mais recentes. As consequências da incompatibilidade de versão são descritas na seção Tratamento de incompatibilidade de versão .

Tratamento de incompatibilidade de versão

A versionFailureStrategy configuração determina o que acontece quando uma versão da instância de orquestração não corresponde à atual defaultVersion.

Configuration:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>",
      "versionFailureStrategy": "Reject"
    }
  }
}

Estratégias disponíveis:

  • Reject (padrão): não processe a orquestração. A instância de orquestração permanece em seu estado atual e pode ser repetida posteriormente quando um trabalho compatível ficar disponível. Essa estratégia é a opção mais segura, pois preserva o estado de orquestração.

  • Fail: falha na orquestração. Essa estratégia encerra imediatamente a instância de orquestração com um estado de falha, o que pode ser apropriado em cenários em que incompatibilidades de versão indicam problemas sérios de implantação.

Iniciando novas orquestrações e sub-orquestrações com versões específicas

Por padrão, todas as novas instâncias de orquestração são criadas com a atual defaultVersion especificada em sua configuração host.json. No entanto, você pode ter cenários em que precisa criar orquestrações com uma versão específica, mesmo que ela seja diferente do padrão atual.

Quando usar versões específicas:

  • Migração gradual: você deseja continuar criando orquestrações com uma versão mais antiga mesmo depois de implantar uma versão mais recente.
  • Cenários de teste: você precisa testar o comportamento de versão específico na produção.
  • Situações de reversão: você precisa reverter temporariamente à criação de instâncias com uma versão anterior.
  • Fluxos de trabalho específicos à versão: diferentes processos de negócios exigem diferentes versões de orquestração.

Você pode substituir a versão padrão fornecendo um valor de versão específico ao criar novas instâncias de orquestração usando as APIs do cliente de orquestração. Isso permite um controle refinado sobre qual versão cada nova instância de orquestração usa.

[Function("HttpStart")]
public static async Task<HttpResponseData> HttpStart(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    var options = new StartOrchestrationOptions
    {
        Version = "1.0"
    };
    
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("ProcessOrderOrchestrator", orderId, options);

    // ...
}

Você também pode iniciar sub-orquestrações com versões específicas de dentro de uma função orquestradora:

[Function("MainOrchestrator")]
public static async Task<string> RunMainOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var subOptions = new SubOrchestratorOptions
    {
        Version = "1.0"
    };
    
    var result = await context.CallSubOrchestratorAsync<string>("ProcessPaymentOrchestrator", orderId, subOptions);
    
    // ...
}

Remover caminhos de código herdados

Com o tempo, talvez você queira remover caminhos de código herdados de suas funções de orquestrador para simplificar a manutenção e reduzir a dívida técnica. No entanto, a remoção do código deve ser feita com cuidado para evitar a interrupção de instâncias de orquestração existentes.

Quando for seguro remover o código herdado:

  • Todas as instâncias de orquestração que usam a versão antiga foram concluídas (bem-sucedidas, com falha ou encerradas)
  • Nenhuma nova instância de orquestração será criada com a versão antiga
  • Você verificou por meio do monitoramento ou consulta que nenhuma instância está em execução com a versão herdada
  • Um período de tempo suficiente passou desde que a versão antiga foi implantada pela última vez (considerando seus requisitos de continuidade de negócios)

Práticas recomendadas para remoção:

  • Monitorar instâncias em execução ativamente: use as APIs de gerenciamento do Durable Functions para consultar instâncias usando versões específicas.
  • Definir políticas de retenção: defina por quanto tempo você pretende manter a compatibilidade com versões anteriores.
  • Remova incrementalmente: considere remover uma versão de cada vez em vez de várias versões simultaneamente.
  • Remoção de documento: mantenha registros claros de quando as versões foram removidas e por quê.

Warning

Remover caminhos de código herdados enquanto as instâncias de orquestração ainda estão executando essas versões pode causar falhas determinísticas de reprodução ou comportamento inesperado. Sempre verifique se nenhuma instância está usando a versão herdada antes de remover o código.

Práticas recomendadas

Gerenciamento de versões

  • Use o versionamento multipartido: adote um esquema de versionamento consistente como major.minor.patch.
  • Alterações críticas do documento: documente claramente quais alterações requerem uma nova versão.
  • Planejar o ciclo de vida da versão: defina quando remover caminhos de código herdados.

Organização de código

  • Lógica de versão separada: use ramificações claras ou métodos separados para versões diferentes.
  • Preservar o determinismo: evite modificar a lógica de versão existente uma vez implantada. Se as alterações forem absolutamente necessárias (como correções críticas de bug), verifique se elas mantêm o comportamento determinístico e não alteram a sequência de operações ou esperem que as versões mais recentes do orquestrador falhem ao processar orquestrações mais antigas.
  • Teste minuciosamente: teste todos os caminhos de versão, especialmente durante as transições.

Monitoramento e observabilidade

  • Informações de versão de log: inclua a versão no registro em log para facilitar a depuração.
  • Monitorar a distribuição de versão: acompanhe quais versões estão sendo executadas ativamente.
  • Configurar alertas: monitore quaisquer erros relacionados à versão.

Troubleshooting

Problemas comuns

  • Problema: As instâncias de orquestração criadas com a versão 1.0 estão falhando após a implantação da versão 2.0

    • Solução: verifique se o caminho de código da versão 1.0 em seu orquestrador permanece exatamente o mesmo. Qualquer alteração na sequência de execução pode interromper a reprodução determinística.
  • Problema: os trabalhadores que executam versões mais antigas do orquestrador não podem executar novas orquestrações

    • Solução: esse é o comportamento esperado. O runtime impede intencionalmente que os trabalhadores mais velhos executem orquestrações com versões mais recentes para manter a segurança. Verifique se todos os trabalhadores estão atualizados para a versão mais recente do orquestrador e se a defaultVersion configuração host.json deles está atualizada adequadamente. Você pode modificar esse comportamento se necessário usando as opções de configuração avançadas (consulte Uso avançado para obter detalhes).
  • Problema: as informações de versão não estão disponíveis no orchestrator (context.Version ou context.getVersion() são nulas, independentemente da defaultVersion configuração)

    • Solução: verifique a seção Pré-requisitos para garantir que seu ambiente atenda a todos os requisitos de versionamento de orquestração.
  • Problema: orquestrações de uma versão mais recente estão fazendo um progresso muito lento ou estão completamente paralisadas

    • Solução: o problema pode ter causas raiz diferentes:
      1. Trabalhos mais recentes insuficientes: verifique se um número suficiente de trabalhadores que contêm uma versão igual ou superior em defaultVersion está implantado e ativo para lidar com as orquestrações mais recentes.
      2. Interferência de roteamento de orquestração de trabalhadores mais antigos: trabalhadores antigos podem interferir no mecanismo de roteamento de orquestração, tornando mais difícil para os novos trabalhadores pegar orquestrações para processamento. Isso pode ser especialmente perceptível ao usar determinados provedores de armazenamento (Armazenamento do Azure ou MSSQL). Normalmente, a plataforma do Azure Functions garante que os trabalhadores antigos sejam descartados logo após uma implantação, portanto, qualquer atraso normalmente não é significativo. No entanto, se você estiver usando uma configuração que permita controlar o ciclo de vida de trabalhadores mais velhos, verifique se os trabalhadores mais antigos acabarão sendo desligados. Como alternativa, considere o uso do Agendador de Tarefas Duráveis, pois ele fornece um mecanismo de roteamento aprimorado menos suscetível a esse problema.

Próximas etapas