Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo mostra como adotar a serialização de carga útil com segurança de tipo (também chamada ciente de tipo) em um aplicativo Durable Functions existente que usa o modelo de programação do Python. A serialização com segurança de tipo valida payloads desserializados em relação a um tipo esperado e permite que você ative o modo estrito, que elimina o risco de desserialização de payloads não confiáveis.
Adotar a serialização tipo segura é uma prática recomendada para todo aplicativo Durable Functions que usa Python, incluindo aplicativos que não sejam sensíveis à segurança. Isso ajuda a detectar logo no início bugs de incompatibilidade de tipos, porque o SDK valida cada payload de acordo com o tipo que seu código espera, em vez de reconstruir silenciosamente o tipo indicado pelos dados armazenados. O modo estrito também torna seu aplicativo mais resistente à desserialização de payloads não confiáveis, o que deixa seu código mais seguro. O azure-functions SDK anuncia o modo estrito como uma boa prática, e este artigo guia você como adotá-lo de forma incremental, começando por etapas retrocompatíveis.
O recurso é entregue em dois pacotes que funcionam juntos:
-
azure-functionsfornece aos serializadores centralizados (df_dumps/df_loads) validação de tipos opcional e suporte a tipagem estrita. -
azure-functions-durableencaminha toda a serialização de payloads do Durable Functions por meio desses serializadores e adiciona o parâmetroexpected_typee a descoberta automática de tipos às APIs de orquestração e de entidade.
Para informações sobre quais dados Durable Functions persistem e como tipos personalizados são serializados, veja Persistência e serialização de dados em Durable Functions.
O que muda
Antes deste recurso, Durable Functions desserializava cargas de objetos personalizados lendo os campos __module__ e __class__ incorporados ao JSON armazenado e chamando importlib.import_module() para localizar a classe. Não houve verificação de que a classe no payload correspondia ao tipo que seu código esperava.
A serialização com segurança de tipos adiciona:
- Um argumento opcional
expected_typenas APIs de orquestração e de entidade que desserializam uma carga útil. -
Descoberta automática de tipos que lê a anotação do tipo de retorno da sua atividade decorada com v2 e das funções de suborquestrador, e a utiliza como
expected_typesem exigir nenhuma alteração no código. - Um modo estrito, ativado por meio da variável de ambiente
AZURE_FUNCTIONS_DURABLE_STRICT_TYPING, que transforma incompatibilidades de tipo em erros fatais e desserializa objetos personalizados sem chamarimportlib.import_module().
O formato de serialização permanece inalterado. Tipos internos ainda são serializados como JSON simples, e objetos personalizados ainda usam a convenção {"__class__", "__module__", "__data__"}. Isso significa que o modo solto é totalmente compatível com versões anteriores: históricos existentes e orquestrações em voo continuam a se desserializar como antes.
Pré-requisitos
Um aplicativo Durable Functions existente que usa o modelo de programação Python (v1 ou v2).
As seguintes versões mínimas de pacotes, que entregam os serializadores centralizados
df_dumps/df_loads:Versão do Python Versão mínima azure-functions3.13 e depois 2.2.0 3.10 – 3.12 1.26.0 azure-functions-durable1.6.0 ou posterior.
Note
Se o pacote instalado azure-functions não fornecer df_dumps / df_loads, o Durable Functions retorna ao pipeline de serialização legado. O formato JSON persistente permanece o mesmo, mas o argumento e o expected_type modo estrito não têm efeito. Atualize para as versões da tabela anterior para permitir a serialização validada por tipo.
Modo solto comparado ao modo estrito
A serialização type-safe possui dois modos.
| Behavior | Modo solto (padrão) | Modo rigoroso |
|---|---|---|
| Aceitar | Sempre Ativo | Defina AZURE_FUNCTIONS_DURABLE_STRICT_TYPING para 1, true, ou yes |
| Tipos incompatíveis | Registra um aviso, depois volta ao decodificador legado | Aumenta TypeError |
| Decodificação de objetos personalizados | Usa importlib.import_module() (caminho legado) |
Chama expected_type.from_json() diretamente; nunca chama import_module |
to_json
/
from_json Contrato |
Inalterado | Deve ser simétrico e produzir dados serializáveis em JSON de forma nativa (veja Atualização de to_json e from_json) |
| Compatível com versões anteriores | Yes | Não. Requer as alterações de código |
O modo solto é seguro para adoção imediata porque nunca muda o comportamento para cargas úteis corretamente tipadas. O modo estrito é uma mudança deliberada e que reforça a segurança e que exige as etapas de migração que seguem.
Migrar incrementalmente
Adote a serialização segura por tipos em fases. Os passos 1 e 2 são compatíveis com versões anteriores e seguros para enviar sozinhos. Complete os passos 3 e 4 apenas quando estiver pronto para ativar o modo estrito.
Passo 1: Atualize os pacotes
Atualize os requisitos do seu app para as versões mínimas em Pré-requisitos. Por exemplo, em requirements.txt:
azure-functions>=2.2.0
azure-functions-durable>=1.6.0
Depois que você atualiza, seu app continua rodando no modo solto sem mudança de comportamento. Você não precisa fazer outras mudanças para manter seu app funcionando.
Passo 2: Adote a validação de tipos em modo flexível
No modo solto, forneça o tipo esperado para que o SDK possa validar cargas desserializadas e registrar um aviso sobre qualquer incompatibilidade. Você pode fornecer o tipo de três maneiras e combiná-las conforme necessário.
Adicione anotações de tipo de retorno às atividades e aos suborquestradores. No modelo de programação Python v2, o SDK descobre automaticamente a anotação de retorno e a usa para validar o resultado. Não é necessária mudança de local de chamada.
@myApp.activity_trigger(input_name="city")
def get_weather(city: str) -> WeatherReport:
return WeatherReport(city=city, temperature_c=21)
@myApp.orchestration_trigger(context_name="context")
def orchestrator(context: df.DurableOrchestrationContext):
# The WeatherReport return annotation on get_weather is discovered
# automatically and used to validate the result.
report = yield context.call_activity("get_weather", "Seattle")
return report.temperature_c
Passe expected_type explicitamente. Uma anotação explícita expected_type tem precedência sobre uma anotação descoberta. Use quando o tipo de retorno não for uma classe concreta. Por exemplo, aliases genéricos como list[Order] ou Optional[Order] não podem ser descobertos automaticamente.
orders = yield context.call_activity("get_orders", customer_id, expected_type=list)
O expected_type argumento está disponível nessas APIs de orquestração:
-
call_activityecall_activity_with_retry -
call_sub_orchestratorecall_sub_orchestrator_with_retry call_entitywait_for_external_eventget_input
E nessas APIs de entidade, por meio de DurableEntityContext:
get_stateget_input
Declare o tipo de entrada de orquestração no gatilho. Use o argumento input_type em orchestration_trigger para que context.get_input() valide a entrada. Um local de chamada expected_type em get_input() tem precedência.
@myApp.orchestration_trigger(context_name="context", input_type=OrderRequest)
def orchestrator(context: df.DurableOrchestrationContext):
request = context.get_input() # validated against OrderRequest
...
Após esta etapa, execute seu aplicativo e verifique nos logs se há avisos de incompatibilidade de tipos no logger azure.functions.DurableFunctions. Resolva quaisquer avisos antes de passar para o modo estrito. Como essa etapa só adiciona avisos, é seguro implantá-la sozinha.
Dica
A descoberta automática de tipos resolve apenas objetos concretos type . Aliases genéricos como list[Order], dict[str, Order] e Optional[Order] são resolvidos como "sem informação de tipo", e a decodificação recorre à resolução somente por módulo. Forneça expected_type explicitamente quando precisar de validação para essas formas.
Passo 3: Atualize to_json e from_json para o modo estrito
O modo estrito altera o contrato para tipos personalizados. No modo estrito, to_json() deve retornar um valor que json.dumps possa serializar nativamente, como dicionários, listas, cadeias de caracteres, números, valores booleanos ou None. Você deve serializar explicitamente objetos personalizados aninhados em vez de retorná-los como instâncias, e from_json() deve reconstruí-los de forma simétrica.
Esse requisito remove cadeias de caracteres __module__ dos payloads armazenados em todos os níveis de aninhamento, de modo que a desserialização não precisa mais resolver os nomes de tipo nos dados do payload.
class Order:
def __init__(self, item, hat):
self.item = item
self.hat = hat
@staticmethod
def to_json(obj):
return {
"item": obj.item,
"hat": Hat.to_json(obj.hat), # explicit, not obj.hat
}
@staticmethod
def from_json(data):
return Order(
item=data["item"],
hat=Hat.from_json(data["hat"]), # symmetric
)
Gerencie cargas úteis legadas em voo durante a implantação. Se seu app ainda pode ler cargas que foram escritas em modo solto antes da atualização, faça from_json tolerar ambos os formatos. Um valor aninhado codificado de forma flexível chega como uma instância já reconstruída (o mecanismo legado object_hook é acionado), enquanto um valor codificado de forma estrita chega como um dicionário simples.
@staticmethod
def from_json(data):
hat_data = data["hat"]
if isinstance(hat_data, Hat):
hat = hat_data # loose-encoded: object already built
else:
hat = Hat.from_json(hat_data) # strict-encoded: plain dict
return Order(item=data["item"], hat=hat)
Passo 4: Ativar o modo estrito
Defina a configuração do aplicativo AZURE_FUNCTIONS_DURABLE_STRICT_TYPING como 1, true ou yes (sem diferenciar maiúsculas de minúsculas).
No seu arquivo local local.settings.json:
{
"Values": {
"AZURE_FUNCTIONS_DURABLE_STRICT_TYPING": "true"
}
}
Ou como uma configuração de aplicativo no seu app de funções:
az functionapp config appsettings set --name <APP_NAME> --resource-group <RESOURCE_GROUP> --settings AZURE_FUNCTIONS_DURABLE_STRICT_TYPING=true
No modo estrito:
- Incompatibilidades de tipo geram
TypeErrorem vez de emitir um aviso. - Objetos personalizados são desserializados por chamada
expected_type.from_json()direta, portantoimport_modulenunca são usados. - Qualquer local de chamada que desserializa um objeto personalizado sem um
expected_typegeraTypeError. Certifique-se de que cada site de chamada forneça um tipo por meio de um dos mecanismos do Passo 2 antes de ativar o modo estrito. - Entradas de funções de atividade não podem ser objetos personalizados. Veja a observação a seguir.
Importante
No modo estrito, a entrada de uma função de atividade não pode ser um objeto personalizado. Quando o host invoca uma atividade, o azure-functions conversor de gatilho de atividade desserializa a entrada sem um expected_type, porque o trabalhador Functions não encaminha a anotação do tipo de parâmetro da atividade para o conversor. Portanto, uma entrada de objeto personalizado falha com um ValueError. Passe as entradas de atividade como valores nativamente serializáveis em JSON, como dicionários, listas, strings, números, booleanos ou None. Se você precisar enviar um objeto personalizado, converta-o com seu método to_json() antes da chamada e reconstrua-o com from_json() dentro da atividade. Essa limitação se aplica apenas a entradas de atividade. Valores de retorno de atividade, entradas de orquestração e de entidade, estado da entidade e cargas de eventos externos oferecem suporte a tipos personalizados no modo estrito quando você fornece um tipo.
Importante
Ative o modo estrito somente depois que todas as instâncias do aplicativo forem atualizadas e que quaisquer orquestrações em andamento que carreguem históricos codificados de forma flexível sejam concluídas, ou que os métodos from_json tolerem ambos os formatos (Passo 3). Uma orquestração que começou antes da atualização reproduz sua história original, codificada de forma solta. Se seu código não consegue decodificar esse histórico no modo rigoroso, a reprodução falha.
Implicações de versionamento para orquestrações existentes
A atualização para a serialização com segurança de tipos interrompe as orquestrações em execução se os tipos de dados mudarem em comparação com a implementação legada. Cada vez que uma orquestração continua, ela reproduz sua história armazenada. Se um site de decodificação agora espera um tipo que não corresponde ao que um payload mais antigo armazenava, o modo estrito levanta a TypeError que não estava presente quando o histórico foi escrito pela primeira vez, e esse novo erro quebra a orquestração. Duas mudanças comuns na migração introduzem esse descompasso:
- Um caminho que antes carregava mais de um tipo. Se um único caminho de desserialização, como um resultado de atividade, antes podia retornar diferentes tipos de objeto, e agora você passa a anotá-lo com um único
expected_type, um payload armazenado que usava um tipo diferente deixa de corresponder e falha na decodificação. - Tipos personalizados usados como entradas de atividade. Como as entradas de atividade não podem ser objetos personalizados no modo estrito, adotar o modo estrito exige que você substitua essas entradas por valores serializáveis em JSON, o que altera a estrutura do payload persistida pelas instâncias em execução.
De forma mais geral, qualquer alteração que faça o tipo armazenado de uma carga útil diferente do tipo que um local de decodificação agora espera causa o mesmo erro. Por exemplo, renomear ou mover uma classe personalizada após a persistência de suas instâncias introduz a mesma incompatibilidade.
Para migrar com segurança, use uma destas abordagens:
- Recomendado: dividir o lançamento com a versão de orquestração. Use versionamento por orquestração com a
Strictestratégia de correspondência de versões para que seus novos trabalhadores em modo estrito processem apenas as orquestrações que começaram na nova versão. Essa prática recomendada permite a coexistência de ambas as versões durante um upgrade contínuo e evita falhas de reprocessamento. - Alternativa: drene primeiro. Deixe todas as orquestrações em voo terminarem e então ative o modo estrito.
Antes de ativar o modo estrito em produção, confirme que cada site de decodificação de objetos personalizados fornece um tipo e que suas classes personalizadas mantêm o mesmo nome e módulo que tinham quando as instâncias em execução mantiveram seus payloads.
Para orientações mais amplas sobre como implantar com segurança mudanças que afetam orquestrações em execução, veja Versioning in Durable Functions.
Fortalecimento de segurança
O modo estrito reforça a desserialização de cargas de objetos personalizados. Em vez de confiar nos nomes de módulos e classes embutidos em dados armazenados ou recebidos para localizar um tipo, o modo estrito reconstrói objetos personalizados usando expected_type fornecido pelo seu código, e a saída de to_json() no modo estrito não preserva nomes de módulos em nenhum nível de aninhamento. Essa mudança elimina a necessidade de resolver nomes de tipo arbitrários a partir dos dados da carga útil durante a desserialização, o que representa uma melhoria de defesa em profundidade em vez de depender das informações de tipo contidas na carga útil.
Se seus payloads puderem conter dados sensíveis, consulte também Como trabalhar com dados sensíveis.