本文將教你如何在現有使用 Python 程式設計模型的 Durable Functions 應用程式中採用型別安全(也稱為型別感知)有效載荷序列化。 型別安全序列化能驗證已解序列化的有效載荷是否符合預期型別,並允許你選擇加入強化 嚴格模式 ,以消除不可信負載反序列化的風險。
採用型別安全序列化是所有使用 Python 的 Durable Functions 應用程式(包括不具安全敏感性的應用程式)的推薦最佳實務。 它幫助你及早發現型別不匹配的錯誤,因為 SDK 會根據你的程式碼預期類型驗證每個有效載荷,而不是默默重建儲存資料名稱的類型。 嚴格模式還可進一步增強應用程式對不受信任酬載反序列化的防護,讓你的程式碼更加安全。
azure-functions SDK 宣稱嚴格模式是最佳實務,本文將引導你逐步採用,從向下相容的步驟開始。
此功能透過兩個協同運作的套件提供:
-
azure-functions提供集中式序列化器(df_dumps/df_loads),並提供可選的型別驗證與嚴格型別支援。 -
azure-functions-durable會將 Durable Functions 的所有承載資料序列化一律透過這些序列化器處理,並為協調流程和實體 API 新增expected_type參數與自動類型探索功能。
關於哪些資料 Durable Functions 會持續存在,以及自訂類型如何序列化,請參見 Durable Functions 中的資料持久化與序列化。
改變了什麼
在此功能推出之前,Durable Functions 會透過讀取已儲存 JSON 中嵌入的 __module__ 和 __class__ 欄位,並呼叫 importlib.import_module() 來尋找類別,藉此將自訂物件承載資料反序列化。 沒有檢查payload中的類別是否符合你的程式碼預期類型。
型別安全的序列化新增:
- 在用於還原序列化承載資料的協調流程和實體 API 上,有一個可選的
expected_type引數。 -
自動型別探索會讀取使用 v2 裝飾的活動函式和子協調器函式的回傳型別註解,並將其作為
expected_type,而無須變更任何程式碼。 - 一種透過 環境變數啟用的
AZURE_FUNCTIONS_DURABLE_STRICT_TYPING,會將型別不符視為嚴重錯誤,並在不呼叫importlib.import_module()的情況下反序列化自訂物件。
序列化格式未變。 內建型別仍會序列化成純 JSON,自訂物件仍沿用此 {"__class__", "__module__", "__data__"} 慣例。 這表示鬆散模式完全向下相容:現有的歷程記錄和執行中的協調流程仍會如先前一樣進行反序列化。
先決條件
一個現有的 Durable Functions 應用程式,使用 Python 程式設計模型(v1 或 v2)。
下列為隨附集中式
df_dumps/df_loads序列化器的最低套件版本:Python 版本 最低版本 azure-functions3.13 及以後版本 2.2.0 3.10 – 3.12 1.26.0 azure-functions-durable1.6.0 或更新版本。
Note
如果已安裝的 azure-functions 套件未提供 df_dumps / df_loads,Durable Functions 會回退到舊版序列化管線。 持久化的 JSON 格式保持不變,但 expected_type 參數和嚴格模式沒有影響。 升級至前表中的版本以啟用型別驗證序列化。
鬆散模式與嚴格模式的比較
型別安全的序列化有兩種模式。
| 行為 | 鬆散模式(預設) | 嚴格模式 |
|---|---|---|
| 選擇加入 | 始終開啟 | 設 AZURE_FUNCTIONS_DURABLE_STRICT_TYPING 為 1、 true或 yes |
| 類型不符 | 記錄警告,然後退回到舊版解碼器 | 調薪 TypeError |
| 自訂物件解碼 | 使用 importlib.import_module()(舊版路徑) |
直接呼叫 expected_type.from_json();絕不呼叫 import_module |
to_json
/
from_json 合約 |
未更改 | 必須對稱且能產生原生可序列化的 JSON 資料(參見 更新 to_json 和 from_json) |
| 向下相容 | Yes | No. 需要修改程式碼 |
鬆散模式可以立即採用,因為它不會改變正確類型負載的行為。 嚴格模式是一種刻意且強化安全機制的變更,需要後續的遷移步驟。
逐步遷移
分階段採用型別安全序列化。 步驟1和步驟2是向下相容的,可以單獨出貨。 只有當你準備好啟用嚴格模式時,才完成步驟3和4。
步驟 1:升級套件
將你的應用程式需求更新到 先修條件中的最低版本。 例如,在 requirements.txt中:
azure-functions>=2.2.0
azure-functions-durable>=1.6.0
升級後,應用程式仍維持鬆散模式,行為沒有改變。 你不需要做其他任何修改來維持現有的應用程式運作。
步驟 2:採用鬆散模式型別驗證
在鬆散模式下,提供預期型別,讓 SDK 能驗證反序列化的有效載荷,並在任何不匹配時記錄警告。 你可以用三種方式供應這種字體,並根據需要混合。
在活動和子協調器中加入回傳類型註解。 在 Python v2 程式設計模型中,SDK 會自動發現回傳註解並用它來驗證結果。 不需要更換通話地點。
@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
明確地傳遞 expected_type。 明確指定的 expected_type 會優先於偵測到的註解。 當回傳類型不是具體類別時才用它。 例如,像 或 list[Order]Optional[Order] 這類通用別名無法自動被發現。
orders = yield context.call_activity("get_orders", customer_id, expected_type=list)
該 expected_type 論證可在以下編排 API 中找到:
-
call_activity與call_activity_with_retry -
call_sub_orchestrator與call_sub_orchestrator_with_retry call_entitywait_for_external_eventget_input
而在這些實體 API 上,透過 DurableEntityContext:
get_stateget_input
在觸發器上宣告編排輸入類型。 用 input_type 參數 on orchestration_trigger 來驗證 context.get_input() 輸入。
expected_type 上的呼叫位置 get_input() 具有優先權。
@myApp.orchestration_trigger(context_name="context", input_type=OrderRequest)
def orchestrator(context: df.DurableOrchestrationContext):
request = context.get_input() # validated against OrderRequest
...
完成這步驟後,執行你的應用程式,並觀察記錄器下方 azure.functions.DurableFunctions 的日誌是否有類型不符的警告。 在進入嚴格模式之前,先解決任何警告。 因為這個步驟只會增加警告,所以單獨部署是安全的。
Tip
自動型別探索只會解析具體的 type 物件。 像 list[Order]、 dict[str, Order]、 Optional[Order] 等通用別名會解析為「無型別資訊」,解碼則退回到僅有模組的解析。 當你需要這些形狀的驗證時,請明確提供 expected_type 。
步驟 3:更新 to_json 和 from_json 以支援嚴格模式
嚴格模式會改變自訂類型的合約。 在嚴格模式下,to_json() 必須回傳一個 json.dumps 可原生序列化的值,例如字典、清單、字串、數值、布林值或 None。 你必須明確序列化巢狀的自訂物件,而不是以實例形式回傳,並且 from_json() 必須對稱地重建它們。
此需求會在所有巢狀層級中,從儲存的有效載荷移除 __module__ 字串,因此反序列化不再需要從有效載荷資料中解析型別名稱。
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
)
在部署期間處理飛行中的舊有有效載荷。 如果你的應用程式在升級後仍可能讀取到升級前以鬆散模式寫入的承載資料,請讓 from_json 同時接受這兩種格式。 鬆散編碼的巢狀值會以已重建的實例形式出現(legacy object_hook 觸發),而嚴格編碼的值則以純字令形式出現。
@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)
步驟 4:啟用嚴格模式
將應用程式設定設AZURE_FUNCTIONS_DURABLE_STRICT_TYPING為 1、 或 trueyes (不區分大小寫)
在本機的 local.settings.json 中:
{
"Values": {
"AZURE_FUNCTIONS_DURABLE_STRICT_TYPING": "true"
}
}
或者在你的函式應用程式中作為應用程式設定:
az functionapp config appsettings set --name <APP_NAME> --resource-group <RESOURCE_GROUP> --settings AZURE_FUNCTIONS_DURABLE_STRICT_TYPING=true
在嚴格模式下:
- 型別不符時會引發
TypeError,而不是記錄警告。 - 自訂物件是透過直接呼叫
expected_type.from_json()來反序列化,因此import_module從不使用。 - 任何將自訂物件反序列化而未使用
expected_type的呼叫位置都會引發TypeError。 在啟用嚴格模式前,請確保每個此類通話網站都透過 步驟2 中的某一機制提供一個類型。 - 活動函數 的輸入 不能是自訂物件。 請參閱下列附註。
Important
在嚴格模式下,活動函式的 輸入 不能是自訂物件。 當主機叫用活動時,azure-functions 活動觸發程序轉換器會在沒有 expected_type 的情況下將輸入還原序列化,因為 Functions 工作器不會將活動的參數類型註解轉送給轉換器。 因此,自訂物件輸入會因出現 ValueError 而失敗。 改以原生可序列化的 JSON 值傳遞活動輸入,例如字典、清單、字串、數字、布林值等 None。 如果你需要傳送自訂物件,請在呼叫前先使用其 to_json() 方法將其轉換,然後在 activity 內使用 from_json() 重建它。 此限制僅適用於活動輸入。 活動回傳值、編排和實體輸入、實體狀態,以及外部事件承載資料,在嚴格模式下只要提供型別,都支援自訂型別。
Important
只有在所有應用程式執行個體都已升級完成,且攜帶鬆散編碼歷程記錄的執行中協調流程皆已完成,或你的 from_json 方法能同時容忍這兩種型態(步驟 3)之後,才啟用嚴格模式。 這段在升級前就開始的編曲重播了它原本、鬆散編碼的歷史。 如果你的程式碼在嚴格模式下無法解碼這些歷史,重播就會失敗。
版本管理對現有編排的影響
更新為型別安全序列化後,若有效載荷類型與舊有實作不同,則執行中的編排會中斷。 每當一個管弦樂繼續播放時,它都會重播其儲存的歷史。 如果解碼位置現在預期的型別與舊版承載資料中儲存的型別不符,嚴格模式就會引發 TypeError,而這個錯誤在最初寫入歷史記錄時並不存在,因而導致協調流程中斷。 兩種常見的遷移變更會造成這種不匹配:
- 這條路以前曾經存在多種類型。 如果單一反序列化路徑(例如活動結果)先前可能會傳回不同的物件類型,而你現在以單一
expected_type進行標註,則先前以不同類型儲存的酬載將不再相符,並會導致解碼失敗。 - 用作活動輸入的自訂類型。 因為 在嚴格模式下,活動輸入不能是自訂物件,所以若要採用嚴格模式,你必須將這些輸入改為可進行 JSON 序列化的值,這會改變執行中執行個體已持久化的負載結構。
更廣泛地說,任何使承載資料的儲存型別與解碼端目前預期的型別不一致的變更,都會導致相同的錯誤。 例如,在自訂類別的實例被持久化後重新命名或移動,會產生相同的不匹配。
為了安全遷移,請採用以下方法之一:
- 建議:將推出時間與編排版本分開。 使用編排版本控制搭配
Strict版本比對策略,讓新的嚴格模式工作者只處理在新版本上啟動的編排流程。 這個最佳做法讓兩個版本在 翻滾升級 時能共存,避免重播失敗。 - 另一種選擇:先排水。 讓所有飛行中的管弦樂完成後,再啟用嚴格模式。
在正式環境啟用嚴格模式之前,請先確認每個自訂物件的解碼位置都會提供型別,並且你的自訂類別保有與執行中執行個體將其酬載持久化時相同的名稱與模組。
如需有關安全地部署會影響執行中協調流程之變更的更全面指引,請參閱 Versioning in Durable Functions。
安全性強化
嚴格模式強化了自訂物件有效載荷的反序列化方式。 嚴格模式不會信任已儲存或傳入的承載資料中內嵌的模組和類別名稱來定位型別,而是使用你的程式碼提供的 expected_type 來重建自訂物件;而且,嚴格模式的 to_json() 輸出不會在任何巢狀層級保留模組名稱。 此變更消除了在反序列化時從承載資料中解析任意型別名稱的需求;相較於依賴承載資料中攜帶的型別資訊,這是一種縱深防禦上的改進。
如果你的有效載荷可能包含敏感資料,也請檢視「 處理敏感資料」。