Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo mostra-lhe como adotar a serialização de payloads type-safe (também chamada type-aware) numa aplicação Durable Functions existente que utiliza o modelo de programação Python. A serialização segura por tipo valida cargas úteis deserializadas contra um tipo esperado e permite optar por um modo rigoroso e reforçado que elimina o risco de desserialização de cargas úteis não confiáveis.
Adotar a serialização com segurança de tipos é uma prática recomendada para todas as aplicações do Durable Functions que utilizam Python, incluindo aplicações que não sejam sensíveis em termos de segurança. Ajuda a detetar atempadamente erros de incompatibilidade de tipos, porque o SDK valida cada carga útil com base no tipo que o teu código espera, em vez de reconstruir silenciosamente qualquer tipo indicado pelos dados armazenados. O modo estrito reforça ainda a sua aplicação contra desserialização de cargas não confiáveis, o que torna o seu código mais seguro. O azure-functions SDK anuncia o modo estrito como uma boa prática, e este artigo guia-o como o adotar de forma incremental, começando por passos retrocompatíveis.
A funcionalidade é entregue em dois pacotes que funcionam em conjunto:
-
azure-functionsfornece aos serializadores centralizados (df_dumps/df_loads) validação de tipos opcional e suporte para tipagem estrita. -
azure-functions-durableencaminha toda a serialização da carga útil do Durable Functions por esses serializadores e adiciona o parâmetroexpected_typee a deteção automática de tipos às APIs de orquestração e de entidades.
Para obter contexto sobre quais os dados que o Durable Functions persiste e como os tipos personalizados são serializados, consulte Data persistence and serialization in Durable Functions.
O que muda
Antes desta funcionalidade, o Durable Functions desserializava payloads de objetos personalizados ao ler os campos __module__ e __class__ incorporados no JSON armazenado e ao chamar importlib.import_module() para localizar a classe. Não houve verificação de que a classe no payload correspondia ao tipo que o seu código esperava.
A serialização segura em termos de tipos acrescenta:
- 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 das suas funções de atividade e de suborquestrador com o decorador v2 e utiliza-a como
expected_typesem qualquer alteração ao código. - Um modo estrito, ativado através da variável de ambiente
AZURE_FUNCTIONS_DURABLE_STRICT_TYPING, que transforma incompatibilidades de tipos em erros fatais e desserializa objetos personalizados sem chamarimportlib.import_module().
O formato de serialização mantém-se inalterado. Os tipos incorporados continuam a serializar para JSON simples, e objetos personalizados continuam a usar a {"__class__", "__module__", "__data__"} convenção. Isto significa que o modo solto é totalmente retrocompatível: os históricos existentes e as orquestrações em voo continuam a desserializar-se como antes.
Pré-requisitos
Uma aplicação Durable Functions existente que utiliza o modelo de programação Python (v1 ou v2).
As seguintes versões mínimas dos pacotes, que incluem os serializadores centralizados
df_dumps/df_loads:Versão em Python Versão mínima azure-functions3.13 e posteriores 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 recorre ao pipeline de serialização legado. O formato JSON persistente mantém-se igual, mas o expected_type argumento e o 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 com modo estrito
A serialização segura quanto aos tipos tem dois modos.
| Comportamento | Modo solto (padrão) | Modo restrito |
|---|---|---|
| Aderir | Sempre ligado | Definir AZURE_FUNCTIONS_DURABLE_STRICT_TYPING para 1, true, ou yes |
| Incompatibilidade de tipo | Regista um aviso e depois volta ao descodificador legado | Aumenta TypeError |
| Decodificação de objetos personalizados | Utiliza importlib.import_module() (caminho legado) |
Chama expected_type.from_json() diretamente; nunca chama import_module |
to_json
/
from_json Contrato |
Inalterado | Devem ser simétricos e produzir dados nativamente serializáveis em JSON (ver Update to_json e from_json) |
| Compatível com versões anteriores | Yes | Não. Requer alterações do código |
O modo solto é seguro para adotar imediatamente porque nunca altera o comportamento para cargas úteis corretamente tipadas. O modo estrito é uma alteração deliberada e que reforça a segurança e que requer os passos de migração que se seguem.
Migrar incrementalmente
Adotar a serialização segura por tipos em fases. Os passos 1 e 2 são retrocompatíveis e seguros para enviar sozinhos. Completa os passos 3 e 4 apenas quando estiveres pronto para ativar o modo estrito.
Passo 1: Atualize os pacotes
Atualize os requisitos da sua aplicação 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 de atualizar, a sua aplicação continua a correr em modo solto sem qualquer alteração de comportamento. Não precisas de fazer outras alterações para manter a tua aplicação a funcionar.
Passo 2: Adotar a validação de tipos em modo flexível
Em modo solto, forneça o tipo esperado para que o SDK possa validar cargas deserializadas e registar um aviso sobre qualquer descorrespondência. Pode fornecer o tipo de três formas diferentes 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 usa-a 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 de forma explícita. Uma anotação explícita expected_type tem precedência sobre uma anotação descoberta. Usa-o quando o tipo de retorno não for uma classe concreta. Por exemplo, pseudónimos 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 nestas APIs de orquestração:
-
call_activityecall_activity_with_retry -
call_sub_orchestratorecall_sub_orchestrator_with_retry call_entitywait_for_external_eventget_input
E nestas APIs de entidades, por meio de DurableEntityContext:
get_stateget_input
Declara o tipo de entrada de orquestração no trigger. Utiliza o argumento input_type em orchestration_trigger para que context.get_input() valide a entrada. Um local da chamada em expected_typeget_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 este passo, execute a sua aplicação e acompanhe os registos à procura de avisos de incompatibilidade de tipos no logger azure.functions.DurableFunctions. Resolve quaisquer avisos antes de passares para o modo estrito. Como este passo só adiciona avisos, é seguro ser implementado sozinho.
Tip
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 descodificação reverte para a resolução apenas por módulo. Fornece expected_type explicitamente quando precisares de validação para essas formas.
Passo 3: Atualizar to_json e from_json para o modo estrito
O modo estrito altera o contrato para tipos personalizados. No modo estrito, to_json() deve devolver um valor que json.dumps possa serializar nativamente, como dicts, listas, cadeias, números, booleanos ou None. Deve serializar explicitamente objetos personalizados aninhados em vez de os devolver como instâncias, e from_json() deve reconstruí-los simetricamente.
Este requisito remove __module__ cadeias de caracteres das cargas armazenadas em cada nível de aninhamento, pelo que a desserialização deixa de precisar de resolver nomes de tipos a partir dos dados da carga.
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
)
Gerir cargas úteis legadas em voo durante a implementação. Se a tua aplicação ainda puder ler cargas úteis que foram escritas em modo solto antes da atualização, faz com from_json que tolere ambas as formas. Um valor aninhado vagamente codificado chega como uma instância já reconstruída (o legado object_hook dispara), enquanto um valor codificado estritamente chega como um ditado 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 definição de aplicação AZURE_FUNCTIONS_DURABLE_STRICT_TYPING como 1, true ou yes (sem distinção entre maiúsculas e minúsculas).
No ficheiro local local.settings.json:
{
"Values": {
"AZURE_FUNCTIONS_DURABLE_STRICT_TYPING": "true"
}
}
Ou como uma definição de aplicação na tua aplicação de funções:
az functionapp config appsettings set --name <APP_NAME> --resource-group <RESOURCE_GROUP> --settings AZURE_FUNCTIONS_DURABLE_STRICT_TYPING=true
Em modo estrito:
- As incompatibilidades de tipo geram
TypeErrorem vez de registar um aviso. - Objetos personalizados são desserializados através de chamadas
expected_type.from_json()diretas, pelo queimport_modulenunca são usados. - Qualquer site de chamadas que desserialize um objeto personalizado sem um
expected_typeaumentaTypeError. Certifique-se de que cada local de chamada desse tipo fornece um tipo através de um dos mecanismos indicados no Passo 2 antes de ativar o modo estrito. - As entradas das funções de atividade não podem ser objetos personalizados. Ver nota seguinte.
Importante
No modo estrito, a entrada de uma função de atividade não pode ser um objeto personalizado. Quando o anfitrião invoca uma atividade, o conversor de acionador de atividade azure-functions desserializa a entrada sem um expected_type, porque o processo de trabalho do Functions não encaminha a anotação de tipo do parâmetro da atividade para o conversor. Uma entrada de objeto personalizado falha, portanto, com um ValueError. Em vez disso, passe as entradas da atividade como valores nativamente serializáveis em JSON, como dicionários, listas, cadeias de caracteres, números, valores booleanos ou None. Se precisa de enviar um objeto personalizado, converta-o com o método to_json() antes da chamada e reconstrua-o com from_json() dentro da atividade. Esta limitação aplica-se apenas a entradas de atividade. Os valores de retorno da atividade, as entradas da orquestração e da entidade, o estado da entidade e as cargas úteis de eventos externos suportam todos os tipos personalizados no modo estrito quando é fornecido um tipo.
Importante
Ative o modo estrito apenas depois de todas as instâncias da aplicação serem atualizadas e quaisquer orquestrações em voo que contenham históricos soltos codificados forem esgotadas ou depois de os seus from_json métodos tolerarem ambas as formas (Passo 3). Uma orquestração que começou antes da atualização repete a sua história original, codificada de forma solta. Se o teu código não conseguir decifrar esse histórico em modo rigoroso, a repetição falha.
Implicações de versionamento para orquestrações existentes
A atualização para serialização segura por tipos interrompe as orquestrações em execução se os tipos de payload mudarem em relação à implementação legada. Cada vez que uma orquestração continua, ela repete a sua história armazenada. Se um ponto de descodificação agora espera um tipo que não corresponde ao que um payload antigo armazenou, o modo estrito gera um TypeError que não estava presente quando o histórico foi escrito pela primeira vez, e esse novo erro interrompe a orquestração. Duas alterações comuns na migração introduzem esta incompatibilidade:
- Um caminho que anteriormente tinha mais do que um tipo. Se um único caminho de desserialização, como um resultado de atividade, pudesse anteriormente devolver diferentes tipos de objetos e agora passar a anotá-lo com um único
expected_type, um payload armazenado com um tipo diferente deixa de corresponder e falha na descodificação. - Tipos personalizados usados como entradas de atividade. Como as entradas de atividade não podem ser objetos personalizados no modo estrito, a adoção do modo estrito exige a alteração dessas entradas para valores serializáveis em JSON, o que altera a estrutura da carga útil que foi persistida pelas instâncias em execução.
De forma mais geral, qualquer alteração que torne 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 das suas instâncias introduz a mesma incompatibilidade.
Para migrar em segurança, utilize uma destas abordagens:
- Recomendado: dividir o lançamento com a versão de orquestração. Use a versão de orquestração com a
Strictestratégia de correspondência de versões para que os seus novos trabalhadores em modo estrito processem apenas as orquestrações que começaram na nova versão. Esta melhor prática permite que ambas as versões coexistam durante uma atualização contínua e evita falhas de repetição. - Alternativa: drenar primeiro. Deixe terminar todas as orquestrações em voo e depois ative o modo estrito.
Antes de ativares o modo estrito em produção, confirma que cada site de decodificação de objetos personalizados fornece um tipo e que as tuas classes personalizadas mantêm o mesmo nome e módulo que tinham quando as instâncias em execução mantinham os seus payloads.
Para orientações mais amplas sobre como implementar com segurança alterações que afetam orquestrações em execução, consulte Versioning in Durable Functions.
Reforço da segurança
O modo estrito reforça a forma como as cargas úteis personalizadas são desserializadas. Em vez de confiar nos nomes de módulos e classes incorporados num payload armazenado ou recebido 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() em modo estrito não preserva os nomes dos módulos em nenhum nível de aninhamento. Esta alteração elimina a necessidade de resolver nomes de tipos arbitrários a partir dos dados da carga útil durante a desserialização, o que representa uma melhoria ao nível da defesa em profundidade em comparação com depender da informação de tipo incluída na carga útil.
Se os seus payloads puderem conter dados sensíveis, consulte também Trabalhar com dados sensíveis.