Overstappen op typeveilige serialisatie in Durable Functions voor Python

Dit artikel laat zien hoe je type-safe (ook wel type-aware genoemd) payloadserialisatie kunt invoeren in een bestaande Durable Functions-app die het Python-programmeermodel gebruikt. Typeveilige serialisatie valideert gedeserialiseerde payloads aan de hand van een verwacht type en biedt de mogelijkheid om een versterkte strict mode in te schakelen die het risico op deserialisatie van onbetrouwbare payloads wegneemt.

Het aannemen van type-safe serialisatie is een aanbevolen best practice voor elke Durable Functions-app die Python gebruikt, inclusief apps die niet beveiligingsgevoelig zijn. Het helpt je type-mismatch bugs vroeg te ontdekken, omdat de SDK elke payload valideert aan de hand van het type dat je code verwacht, in plaats van stilletjes het type van de opgeslagen data te reconstrueren. Strict-modus versterkt je app bovendien tegen deserialisatie van onbetrouwbare payload, wat je code veiliger maakt. De azure-functions SDK adverteert met de strikte modus als best practice, en dit artikel leidt je bij het incrementeel toepassen ervan, te beginnen met achterwaarts compatibele stappen.

De functie wordt geleverd in twee pakketten die samenwerken:

  • azure-functions biedt de gecentraliseerde serializers (df_dumps / df_loads) optionele typevalidatie en ondersteuning voor strikte typering.
  • azure-functions-durablerouteert alle Durable Functions payload-serialisatie door die serializers en voegt de expected_type parameter- en automatische typeontdekking toe aan de orchestratie- en entiteits-API's.

Voor achtergrondinformatie over welke data Durable Functions bewaard blijft en hoe aangepaste types worden geserialiseerd, zie Data persistence and serialization in Durable Functions.

Welke wijzigingen

Vóór deze functionaliteit deserializeerde Durable Functions payloads van aangepaste objecten door de velden __module__ en __class__ in de opgeslagen JSON te lezen en importlib.import_module() aan te roepen om de klasse te vinden. Er was geen controle of de klasse in de payload overeenkwam met het type dat je code verwachtte.

Typeveilige serialisatie voegt het volgende toe:

  • Een optioneel expected_type argument over de orkestratie- en entiteits-API's die een payload deserialiseren.
  • Automatische typedetectie die de annotatie van het retourtype van je met v2 gedecoreerde activiteit- en sub-orchestratorfuncties leest en deze gebruikt als expected_type zonder codewijzigingen.
  • Een strikte modus, gekozen met de AZURE_FUNCTIONS_DURABLE_STRICT_TYPING omgevingsvariabele, die type-mismatches omzet in harde fouten en aangepaste objecten deserialiseert zonder aan te roepen importlib.import_module().

Het serialisatieformaat is ongewijzigd. Ingebouwde types serialiseren nog steeds naar gewone JSON, en aangepaste objecten gebruiken nog steeds de {"__class__", "__module__", "__data__"} conventie. Dit betekent dat loose mode volledig achterwaarts compatibel is: bestaande geschiedenissen en orkestraties tijdens de vlucht blijven deserialiseren zoals voorheen.

Prerequisites

  • Een bestaande Durable Functions-app die het Python-programmeermodel (v1 of v2) gebruikt.

  • De volgende minimale pakketversies, die de gecentraliseerde df_dumps / df_loads serializers leveren:

    Python-versie Minimumversie azure-functions
    3.13 en hoger 2.2.0
    3.10 – 3.12 1.26.0
  • azure-functions-durable 1.6.0 of hoger.

Opmerking

Als het geïnstalleerde azure-functions-pakket df_dumps / df_loads niet bevat, valt Durable Functions terug op de verouderde serialisatiepipeline. Het behoudende JSON-formaat blijft hetzelfde, maar het argument en de expected_type strikte modus hebben geen effect. Upgrade naar de versies in de bovenstaande tabel om typegevalideerde serialisatie mogelijk te maken.

Losse modus vergeleken met strikte modus

Typeveilige serialisatie heeft twee modi.

Gedrag Flexibele modus (standaard) Strikte modus
Inschrijven Altijd ingeschakeld Stel AZURE_FUNCTIONS_DURABLE_STRICT_TYPING in op 1, true, of yes
Typen komen niet overeen Registreert een waarschuwing en grijpt dan terug op de oude decoder Verhogingen TypeError
Decode van aangepaste objecten Gebruikt importlib.import_module() (verouderd pad) Roept expected_type.from_json() rechtstreeks aan; roept import_module nooit aan
to_json / from_json Contract Ongewijzigd Moet symmetrisch zijn en native JSON-serialiseerbare data produceren (zie Update to_json en from_json)
Achterwaarts compatibel Yes No. Wijzigingen in de code vereist

Loose mode is veilig om direct te adopteren omdat het gedrag voor correct getypeerde payloads nooit verandert. Strict-modus is een bewuste, security-versterkende wijziging die de migratiestappen vereist die daarop volgen.

Migreren stapsgewijs

Voer type-veilige serialisatie in fasen aan. Stap 1 en 2 zijn achterwaarts compatibel en veilig om zelfstandig te verzenden. Voltooi stap 3 en 4 alleen als je klaar bent om de strenge modus in te schakelen.

Stap 1: Werk de pakketten bij

Werk de vereisten van je app bij naar de minimale versies in Prerequisites. Bijvoorbeeld, in requirements.txt:

azure-functions>=2.2.0
azure-functions-durable>=1.6.0

Na de upgrade blijft je app in losse modus draaien zonder gedragsverandering. Je hoeft geen andere wijzigingen aan te brengen om je bestaande app werkend te houden.

Stap 2: Accepteer loose-mode type validatie

In losse modus geef je het verwachte type zodat de SDK deserialiseerde payloads kan valideren en een waarschuwing kan registreren bij eventuele mismatch. Je kunt het type op drie manieren leveren en ze naar behoefte mengen.

Voeg return-type annotaties toe aan activiteiten en sub-orchestrators. In het Python v2 programmeermodel ontdekt de SDK automatisch de returnannotatie en gebruikt deze om het resultaat te valideren. Er is geen wijziging van de oproeplocatie nodig.

@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

Geef expected_type expliciet door. Een expliciet expected_type heeft voorrang boven een ontdekte annotatie. Gebruik het wanneer het retourtype geen concrete klasse is. Generieke aliassen zoals list[Order] of Optional[Order] kunnen bijvoorbeeld niet automatisch worden ontdekt.

orders = yield context.call_activity("get_orders", customer_id, expected_type=list)

Het expected_type argument is beschikbaar op deze orkestratie-API's:

  • call_activity en call_activity_with_retry
  • call_sub_orchestrator en call_sub_orchestrator_with_retry
  • call_entity
  • wait_for_external_event
  • get_input

En in deze entiteits-API's, via DurableEntityContext:

  • get_state
  • get_input

Definieer het invoertype van de orchestratie voor de trigger. Gebruik het input_type argument aan orchestration_trigger zodat dat context.get_input() de input valideert. Een aanroepplaats expected_type op get_input() krijgt voorrang.

@myApp.orchestration_trigger(context_name="context", input_type=OrderRequest)
def orchestrator(context: df.DurableOrchestrationContext):
    request = context.get_input()  # validated against OrderRequest
    ...

Na deze stap voer je je app uit en controleer je de logboeken op waarschuwingen voor typeconflicten in de azure.functions.DurableFunctions-logger. Los eventuele waarschuwingen op voordat je overgaat naar de strenge modus. Omdat deze stap alleen waarschuwingen toevoegt, is het veilig om het zelf in te zetten.

Tip

Automatische typeontdekking lost alleen concrete type objecten op. Generieke aliassen zoals list[Order], dict[str, Order] en Optional[Order] worden omgezet in "geen type-informatie", en bij het decoderen wordt teruggevallen op resolutie uitsluitend op moduleniveau. Geef expected_type expliciet wanneer je validatie nodig hebt voor deze vormen.

Stap 3: Werk to_json en from_json bij voor de strikte modus

Strict-modus verandert het contract voor aangepaste types. In strikte modus moet to_json() een waarde retourneren die json.dumps van nature kan serialiseren, zoals woordenboeken, lijsten, tekenreeksen, getallen, booleaanse waarden of None. Je moet geneste custom objecten expliciet serialiseren in plaats van ze als instanties terug te geven, en from_json() ze symmetrisch reconstrueren.

Deze vereiste verwijdert __module__-tekenreeksen uit opgeslagen payloads op elk nestingsniveau, zodat deserialisatie typenamen niet langer hoeft op te lossen op basis van payloadgegevens.

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
        )

Verwerk legacy-payloads die al onderweg zijn tijdens de uitrol. Als je app nog payloads kan lezen die vóór de upgrade in losse mode zijn geschreven, laat from_json dan beide vormen tolereren. Een los-gecodeerde geneste waarde arriveert als een reeds gereconstrueerde instantie (de legacy object_hook wordt geactiveerd), terwijl een streng gecodeerde waarde als een gewone dict arriveert.

    @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)

Stap 4: Schakel de strikte modus in

Stel de AZURE_FUNCTIONS_DURABLE_STRICT_TYPING toepassingsinstelling in op 1, true, of yes (niet hoofdlettergevoelig).

In uw lokale local.settings.json:

{
  "Values": {
    "AZURE_FUNCTIONS_DURABLE_STRICT_TYPING": "true"
  }
}

Of als applicatie-instelling in je functie-app:

az functionapp config appsettings set --name <APP_NAME> --resource-group <RESOURCE_GROUP> --settings AZURE_FUNCTIONS_DURABLE_STRICT_TYPING=true

In strikte modus:

  • Typeconflicten genereren TypeError in plaats van een waarschuwing te loggen.
  • Aangepaste objecten worden gedeserialiseerd door direct aan te roepen expected_type.from_json() , dus import_module wordt nooit gebruikt.
  • Elke aanroepsite die een aangepast object deserialiseert zonder een expected_type verhoogt TypeError. Zorg ervoor dat elke dergelijke oproeplocatie via een van de mechanismen in Stap 2 een type levert voordat je de strenge modus inschakelt.
  • Activiteitsfunctie-invoeren kunnen geen aangepaste objecten zijn. Zie de volgende opmerking.

Important

In de strikte modus kan de invoer van een activiteitsfunctie geen aangepast object zijn. Wanneer de host een activiteit aanroept, deserialiseert de azure-functions converter voor activiteitstriggers de invoer zonder expected_type, omdat de Functions-worker de parameter type-annotatie van de activiteit niet doorstuurt naar de converter. Een custom-object-invoer faalt dus met een ValueError. Geef activiteitsinvoer in plaats daarvan door als JSON-serialiseerbare waarden, zoals dictionaries, lijsten, strings, getallen, booleaanse waarden of None. Als je een aangepast object moet versturen, converteer het dan met zijn to_json() methode vóór de aanroep en reconstrueer het binnen from_json() de activiteit. Deze beperking geldt alleen voor activiteitsinvoer. Activiteitsterugkomstwaarden, orkestratie en entiteitsinvoer, entiteitsstatus en externe gebeurtenispayloads ondersteunen allemaal aangepaste types in strikte modus wanneer je een type aangeeft.

Important

Schakel de strikte modus alleen in nadat alle app-instanties zijn geüpgraded en alle in-flight orkestraties met lose-gecodeerde geschiedenissen zijn verwijderd of je from_json methoden beide vormen tolereren (Stap 3). Een orkestratie die begon vóór de upgrade speelt zijn oorspronkelijke, losjes gecodeerde geschiedenis opnieuw af. Als je code die geschiedenis niet kan decoderen onder de strenge modus, faalt de replay.

Versieeringsimplicaties voor bestaande orkestraties

Bijwerken naar typeveilige serialisatie verstoort actieve orkestraties als de payloadtypen veranderen ten opzichte van de legacy-implementatie. Elke keer dat een orkestratie doorgaat, speelt het zijn opgeslagen geschiedenis opnieuw af. Als een decode-site nu een type verwacht dat niet overeenkomt met wat een oudere payload heeft opgeslagen, roept strict mode een TypeError op die niet aanwezig was toen de geschiedenis voor het eerst werd geschreven, en die nieuwe fout breekt de orkestratie. Twee veelvoorkomende migratieveranderingen introduceren deze mismatch:

  • Een pad dat eerder meer dan één type bevatte. Als een enkel deserialisatiepad, zoals een activiteitsresultaat, voorheen verschillende objecttypes kon teruggeven, en je het nu met één annoteert expected_type, komt een opgeslagen payload die een ander type gebruikte niet meer overeen en kan niet meer decoderen.
  • Aangepaste types gebruikt als activiteitsinvoer. Omdat activiteitsinvoer geen aangepaste objecten in strikte modus kan zijn, vereist het adopteren van strikte modus dat je die invoer verandert in JSON-serialiseerbare waarden, wat de vorm van de payload verandert die draaiende instanties hebben behouden.

Algemener veroorzaakt elke wijziging die het opgeslagen type van een payload laat verschillen van het type dat een decodesite nu verwacht, dezelfde fout. Bijvoorbeeld, het hernoemen of verplaatsen van een aangepaste klasse nadat de instanties ervan zijn opgeslagen introduceert dezelfde inconsistentie.

Om veilig te migreren, gebruik een van deze methoden:

  • Aanbevolen: splits de uitrol met orkestratieversies. Gebruik versiebeheer voor orchestraties met de Strict strategie voor versieovereenkomst, zodat je nieuwe strict-mode workers alleen de orchestraties verwerken die met de nieuwe versie zijn gestart. Deze best practice zorgt ervoor dat beide versies naast elkaar bestaan tijdens een rollende upgrade en voorkomt herhalingsfouten.
  • Alternatief: eerst afvoeren. Laat alle in-flight orkestraties klaar zijn en schakel dan de strenge modus in.

Voordat je de strikte modus in productie inschakelt, controleer of elke decodeerlocatie voor aangepaste objecten een type opgeeft en of je aangepaste klassen dezelfde naam en module behouden als toen actieve instanties hun payloads opsloegen.

Voor bredere richtlijnen voor het veilig implementeren van wijzigingen die uitvoerende orkestraties beïnvloeden, zie Versioning in Durable Functions.

Versterken van de beveiliging

De strikte modus maakt de deserialisatie van payloads van aangepaste objecten strenger. In plaats van te vertrouwen op de module- en klassennamen die zijn opgenomen in een opgeslagen of inkomende payload om een type te vinden, reconstrueert de strikte modus aangepaste objecten met behulp van de expected_type die je code aanlevert, en de to_json()-uitvoer van de strikte modus slaat geen modulenamen op, op welk nestingsniveau dan ook. Deze wijziging verwijdert de noodzaak om willekeurige typenamen uit payloaddata op te lossen tijdens deserialisatie, wat een defensieve verbetering is ten opzichte van het vertrouwen op de type-informatie die in de payload wordt gedragen.

Als je payloads gevoelige data kunnen bevatten, bekijk dan ook Werk met gevoelige data.