Arcjet은 WebAssembly를 코드 SDK로서의 보안과 함께 번들로 제공합니다. 이를 통해 개발자는 PII 감지 및 봇 감지와 같은 일반적인 보안 기능을 코드에서 직접 구현할 수 있습니다. 대부분의 논리는 기본 성능에 가까운 보안 샌드박스를 제공하고 로컬 우선 보안에 대한 우리 철학의 일부인 Wasm에 내장되어 있습니다.
JavaScript에서 다른 기술 스택까지 지원을 구축할 때 여러 플랫폼에서 동일한 코드를 실행하는 기능도 도움이 되지만, 언어 간 번역을 위해서는 중요한 추상화가 필요합니다(저희 Wasm은 Rust에서 컴파일되었습니다).
WebAssembly 구성 요소 모델은 이를 가능하게 하는 강력한 구조이지만, 구조는 이를 둘러싼 구현 및 도구만큼 우수할 수 있습니다. 구성 요소 모델의 경우 이는 호스트(WebAssembly 구성 요소 모델을 실행하는 환경) 및 게스트(모든 언어로 작성되고 구성 요소 모델로 컴파일된 WebAssembly 모듈, 우리의 경우 Rust)에 대한 코드 생성에서 가장 분명합니다.
컴포넌트 모델은 호스트와 게스트 간의 통신을 위한 언어를 정의하며 주로 유형, 기능, 가져오기 및 내보내기로 구성됩니다. 광범위한 언어를 정의하려고 시도하지만 변형, 튜플 및 리소스와 같은 일부 유형은 특정 범용 프로그래밍 언어에 존재하지 않을 수 있습니다.
도구가 이러한 언어 중 하나에 대한 코드를 생성하려고 할 때 작성자는 구성 요소 모델 유형을 해당 범용 언어에 매핑하기 위해 창의력을 발휘해야 하는 경우가 많습니다. 예를 들어 JS 바인딩을 생성하기 위해 jco를 사용하고 이는 { 태그: 문자열, 값: 문자열 } 형태의 JavaScript 개체를 사용하여 변형을 구현합니다. 결과에 대한 특별한 경우도 있습니다<_, _> 오류 변형이 오류로 바뀌고 발생하는 유형입니다.
이 게시물에서는 Wasm 구성 요소 모델이 언어 간 통합을 가능하게 하는 방법, 호스트와 게스트에 대한 코드 생성의 복잡성, Go와 같은 언어에서 관용적 코드를 달성하기 위해 취하는 절충점을 살펴봅니다.
Arcjet에서는 Go 프로그래밍 언어로 작성된 호스트용 코드를 생성하는 도구를 구축해야 했습니다. SDK가 모든 것을 로컬에서 분석하려고 시도하지만 항상 가능한 것은 아니므로 추가 메타데이터로 로컬 결정을 강화하는 Go로 작성된 API가 있습니다.
Go는 설계상 매우 최소한의 구문과 유형 시스템을 갖추고 있습니다. 아주 최근까지 제네릭도 없었고 여전히 상당한 한계가 있습니다. 이로 인해 구성 요소 모델에서 Go로의 codegen이 다양한 방식으로 복잡해집니다.
예를 들어 결과를 생성할 수 있습니다<_, _> 다음과 같이:
type Result[V any] struct { value V err error }
단, 이는 오류 위치에 제공할 수 있는 유형을 제한합니다. 따라서 다음과 같이 코드 생성해야 합니다.
type Result[V any] struct { value V err error }
이 방법은 작동하지만 위에서 정의한 Result 유형과 동일한 의미를 나타내기 위해 종종 val, err := doSomething() 규칙을 사용하는 다른 관용적 Go와 함께 사용하기가 번거롭습니다.
게다가 이 Result를 생성하는 것도 번거롭습니다. Result[int, string]{value: 1, err: ""}. Result 유형을 제공하는 대신 Go 사용자가 생성된 바인딩을 자연스럽게 사용하도록 관용적 패턴을 일치시키고 싶을 것입니다.
언어에 더 자연스럽게 느껴지도록 코드를 생성할 수도 있고 구성 요소 모델 유형에 더 직접적으로 매핑할 수도 있습니다. 두 옵션 모두 사용 사례에 100% 적합하지 않으므로 어느 것이 가장 적합한지 결정하는 것은 도구 작성자의 몫입니다.
Arcjet 툴링의 경우 옵션으로 관용적인 Go 접근 방식을 선택했습니다<_> 결과<_, _> 각각 val, ok := doSomething() 및 val, err := doSomething()에 매핑되는 유형입니다. 변형의 경우 다음과 같이 각 변형이 구현해야 하는 인터페이스를 만듭니다.
type Result[V any, E any] struct { value V err E }
이는 유형 안전성과 불필요한 포장 사이의 적절한 균형을 유지합니다. 물론 포장이 필요한 상황도 있지만, 극단적인 경우로 처리할 수 있습니다.
개발자는 관용적이지 않은 패턴으로 인해 장황하고 유지 관리가 어려운 코드로 인해 어려움을 겪을 수 있습니다. 확립된 규칙을 사용하면 코드가 더 친숙하게 느껴지지만 구현하려면 추가 노력이 필요합니다.
우리는 코드베이스를 이동할 때 무엇을 기대할 수 있는지 알 수 있도록 마찰을 최소화하고 팀의 작업을 더 쉽게 만드는 관용적 경로를 선택하기로 결정했습니다.
도구 작성자가 내려야 할 가장 큰 결정 중 하나는 바인딩의 호출 규칙입니다. 여기에는 가져오기를 컴파일하는 방법/시기, 설정 또는 인스턴스화 및 정리 중에 Wasm 모듈을 컴파일할지 여부를 결정하는 것이 포함됩니다.
Arcjet 코드베이스에서는 성능 최적화를 위해 팩토리/인스턴스 패턴을 선택했습니다. WebAssembly 모듈을 컴파일하는 것은 비용이 많이 들기 때문에 NewBotFactory() 생성자에서 한 번만 수행합니다. 후속 Instantiate() 호출은 빠르고 저렴하므로 프로덕션 워크로드에서 높은 처리량이 가능합니다.
type BotConfig interface { isBotConfig() } func (AllowedBotConfig) isBotConfig() {} func (DeniedBotConfig) isBotConfig() {}
소비자는 NewBotFactory(ctx)를 호출하여 이 BotFactory를 한 번 생성하고 이를 사용하여 Instantiate 메서드를 통해 여러 인스턴스를 생성합니다.
func NewBotFactory( ctx context.Context, ) (*BotFactory, error) { runtime := wazero.NewRuntime(ctx) // ... Imports are compiled here if there are any // Compiling the module takes a LONG time, so we want to do it once and hold // onto it with the Runtime module, err := runtime.CompileModule(ctx, wasmFileBot) if err != nil { return nil, err } return &BotFactory{runtime, module}, nil }
팩토리를 구성할 때 Runtime.CompileModule()을 사용하는 것처럼 모듈이 이미 컴파일되어 있으면 인스턴스화가 매우 빠릅니다.
BotInstance에는 구성 요소 모델 정의에서 내보낸 기능이 있습니다.
func (f *BotFactory) Instantiate(ctx context.Context) (*BotInstance, error) { if module, err := f.runtime.InstantiateModule(ctx, f.module, wazero.NewModuleConfig()); err != nil { return nil, err } else { return &BotInstance{module}, nil } }
일반적으로 BotInstance를 사용한 후에는 메모리 누수를 방지하기 위해 이를 정리하고 싶습니다. 이를 위해 닫기 기능을 제공합니다.
func (i *BotInstance) Detect( ctx context.Context, request string, options BotConfig, ) (BotResult, error) { // ... Lots of generated code for binding to Wazero }
전체 BotFactory를 정리하려면 해당 BotFactory도 닫을 수 있습니다.
type Result[V any] struct { value V err error }
이 모든 API를 함께 사용하여 이 WebAssembly 모듈에서 함수를 호출할 수 있습니다.
type Result[V any, E any] struct { value V err E }
이러한 팩토리 및 인스턴스 구성 패턴은 사용하는 데 더 많은 코드가 필요하지만 Arcjet 서비스의 핫 경로에서 최대한 많은 성능을 달성하기 위해 선택되었습니다.
컴파일 비용을 프론트 로딩함으로써 대기 시간이 가장 중요한 Arcjet 서비스의 핫 경로에서 요청 처리가 최대한 효율적이도록 보장합니다. 이러한 절충안은 초기화 코드에 약간의 복잡성을 추가하지만 요청당 오버헤드가 상당히 낮아지는 이점이 있습니다. 절충점에 대한 논의를 참조하세요.
두 개 이상의 언어를 통합해야 할 때마다 네이티브 FFI를 사용하든 구성 요소 모델을 사용하든 균형을 맞춰야 합니다.
이 게시물에서는 Arcjet에서 직면한 몇 가지 문제와 결정을 내린 이유에 대해 논의했습니다. 모두가 구성 요소 모델 및 WIT와 같은 동일한 기본 요소 세트를 기반으로 구축하는 경우 wit-bindgen 또는 wit-comComponent와 같은 동일한 고품질 기본 요소 세트를 활용할 수 있습니다. 모든 사용 사례에 적합한 도구를 구축하세요. 이것이 바로 표준을 향한 노력이 모든 사람에게 도움이 되는 이유입니다.
WebAssembly 구성 요소 모델은 언어 간 통합을 위한 강력한 추상화를 제공하지만 해당 유형을 Go와 같은 언어로 변환하려면 미묘한 설계 문제가 발생합니다. 관용적인 패턴을 선택하고 선택적으로 성능을 최적화함으로써(예: 팩토리/인스턴스 패턴 사용) 효율성을 유지하면서 자연스러운 개발자 경험을 제공할 수 있습니다.
구성 요소 모델을 중심으로 한 도구가 발전함에 따라 이러한 통합을 더욱 단순화하는 더욱 세련된 codegen 접근 방식을 기대할 수 있습니다.
위 내용은 Wasm 구성 요소 모델 및 관용적 코드 생성의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!