Arcjet 將 WebAssembly 與我們的安全即程式碼 SDK 捆綁在一起。這有助於開發人員直接在程式碼中實現常見的安全功能,例如 PII 檢測和機器人檢測。大部分邏輯都嵌入到 Wasm 中,它為我們提供了一個具有接近本機性能的安全沙箱,並且是我們關於本地優先安全的理念的一部分。
跨平台運行相同程式碼的能力也很有幫助,因為我們建立了從JavaScript 到其他技術堆疊的支持,但它需要一個重要的抽象來在語言之間進行翻譯(我們的Wasm 是從Rust 編譯的)。
WebAssembly 元件模型 是實現這一點的強大構造,但構造只能與它周圍的實現和工具一樣好。對於元件模型,這在主機(執行 WebAssembly 元件模型的環境)和來賓(以任何語言編寫並編譯到元件模型的 WebAssembly 模組;在我們的例子中為 Rust)的程式碼產生中最為明顯。
元件模型定義了主機和來賓之間通訊的語言,主要由類型、函數、匯入和匯出組成。它試圖定義一種廣泛的語言,但某些類型,例如變體、元組和資源,可能不存在於給定的通用程式語言中。
當工具嘗試為其中一種語言產生程式碼時,作者通常需要發揮創意,將元件模型類型對應到該通用語言。例如,我們使用 jco 來產生 JS 綁定,並使用 { tag: string, value: string } 形式的 JavaScript 物件實作變體。它甚至有結果<_ _> 的特殊情況。類型,其中錯誤變體將轉換為錯誤並拋出。
這篇文章探討了 Wasm 元件模型如何實現跨語言整合、主機和來賓程式碼產生的複雜性,以及我們為用 Go 等語言實現慣用程式碼所做的權衡。
在 Arcjet,我們必須建立一個工具來為用 Go 程式語言編寫的主機產生程式碼。儘管我們的 SDK 嘗試在本地分析所有內容,但這並不總是可行,因此我們有用 Go 編寫的 API,它透過附加元資料增強本地決策。
Go 在設計上有一個非常小的語法和類型系統。他們直到最近才推出仿製藥,而且仍然有很大的限制。這使得從組件模型到 Go 的程式碼生成在各個方面都變得複雜。
例如,我們可以產生結果<_ _>;如:
但是,這限制了錯誤位置可以提供的類型。所以我們需要將其編碼為:
這可以工作,但與其他慣用的 Go 一起使用會變得很麻煩,後者通常使用 val, err := doSomething() 約定來指示與我們上面定義的 Result 類型相同的語義。
此外,建構這個 Result 很麻煩:Result[int, string]{value: 1, err: ""}。我們可能希望匹配慣用模式,而不是提供 Result 類型,以便 Go 用戶能夠自然地使用我們產生的綁定。
可以產生使語言感覺更自然的程式碼,也可以更直接地對應到元件模型類型。這兩個選項都不適合 100% 的用例,因此由工具作者決定哪個最有意義。
對於 Arcjet 工具,我們為選項選擇了慣用的 Go 方法<_>;結果<_>類型,分別映射到 val, ok := doSomething() 和 val, err := doSomething()。對於變體,我們建立每個變體需要實現的接口,例如:
這在類型安全和不必要的包裝之間取得了良好的平衡。當然,也有需要包裹的情況,但這些可以作為邊緣情況處理。
開發人員可能會遇到非慣用的模式,導致程式碼冗長且難以維護。使用既定約定使程式碼感覺更熟悉,但確實需要一些額外的努力來實現。
我們決定採取慣用的方式來最大程度地減少摩擦,讓我們的團隊更輕鬆,這樣我們就知道在程式碼庫中移動時會發生什麼。
工具作者需要做出的最大決定之一是綁定的呼叫約定。這包括決定如何/何時編譯導入、是否在設定或實例化期間編譯 Wasm 模組以及清理。
在Arcjet程式碼庫中,我們選擇工廠/實例模式來最佳化效能。編譯 WebAssembly 模組的成本很高,因此我們在 NewBotFactory() 建構函式中執行一次。隨後的 Instantiate() 呼叫既快速又便宜,從而在生產工作負載中實現高吞吐量。
消費者透過呼叫 NewBotFactory(ctx) 來建構此 BotFactory 一次,並使用它透過 Instantiate 方法建立多個實例。
如果模組已經編譯過,實例化會非常快,就像我們在建構工廠時使用runtime.CompileModule() 所做的那樣。
BotInstance 具有從組件模型定義導出的函數。
通常,在使用 BotInstance 後,我們希望清理它以確保不會洩漏記憶體。為此,我們提供了關閉函數。
如果你想清理整個BotFactory,也可以關掉它:
我們可以將所有這些 API 放在一起來呼叫此 WebAssembly 模組上的函數:
這個工廠和實例建置模式需要使用更多程式碼,但選擇它是為了在 Arcjet 服務的熱路徑中實現盡可能多的效能。
透過預先載入編譯成本,我們確保在 Arcjet 服務的熱路徑中(延遲最重要)請求處理盡可能有效率。這種權衡確實增加了初始化程式碼的複雜性,但它的回報是每個請求的開銷大大降低 - 請參閱我們對權衡的討論。
任何時候我們需要整合兩種或多種語言,都需要做出權衡——無論是使用原生 FFI 還是組件模型。
這篇文章討論了我們在 Arcjet 遇到的一些挑戰以及我們決定背後的原因。如果我們都基於同一組原語(例如組件模型和WIT)進行構建,那麼我們都可以利用同一組高品質原語,例如wit-bindgen 或wit-component ,並建立適合每個用例的工具。這就是為什麼制定標準對每個人都有幫助。
WebAssembly 元件模型為跨語言整合提供了強大的抽象,但將其類型轉換為 Go 等語言會帶來微妙的設計挑戰。透過選擇慣用的模式並選擇性地最佳化效能(例如使用工廠/實例模式),我們可以在保持效率的同時提供自然的開發人員體驗。
隨著組件模型工具的發展,我們可以期待更精細的程式碼產生方法來進一步簡化這些整合。
以上是Wasm 組件模型和慣用的程式碼生成的詳細內容。更多資訊請關注PHP中文網其他相關文章!