Wasmコンポーネントの活用:Spin v3.0によるWebアプリ開発
Rustで実装しているSpinアプリに、TypeScriptで実装したビジネスロジックを組み込みました。これはSpin v3.0から利用できるspin-depsプラグインを利用した結果です。この記事では、Wasmコンポーネントとして実装されたビジネスロジックを、同プラグインを利用してSpinアプリに組み込むための手順と、簡単な例について説明します。
TL;DR:
- Spinはマイクロサービスを定義するためのフレームワークです
-
Spin v3.0よりWasmコンポーネントを組み込むためのプラグイン
spin deps
が試験的に追加されました - このプラグインを使うことで、ビジネスロジックの実装とマイクロサービスを実装する言語を切り離すことができます
Spinとは
Spinとはマイクロサービスを作成するためのフレームワークで、次の特徴を持ちます:
- イベント駆動型のフレームワークです
- イベントハンドラーはWasmコンポーネントとして実装されます
- Rust、GoやTypeScriptなど、さまざまな言語でインベントハンドラーを実装できます
詳しくは「RustによるマクロサービスフレームワークSpin入門」という記事をご覧ください。
なお、Spinではインベントハンドラーのことを「コンポーネント」と呼びます。Wasmコンポーネントと紛らわしいので、以降はSpinのコンポーネントのことを「Spinコンポーネント」と呼びます。
Wasmコンポーネントの組み込み
Spin v3.0より、WasmコンポーネントをSpinコンポーネントの依存関係に追加できるようになりました。これにより次のインターフェースに依存するWasmコンポーネントを、Spinコンポーネントに組み込んで利用できるようになります。
なお、上記のパッケージに定義されている全てのインターフェースがSpinコンポーネントに組み込めることを確認してはいません。ただし、以下のインターフェースに依存するWasmコンポーネントを組み込むことはできました:
wasi:io/error@0.2.2
wasi:io/poll@0.2.2
wasi:io/streams@0.2.2
wasi:cli/stdin@0.2.2
wasi:cli/stdout@0.2.2
wasi:cli/stderr@0.2.2
wasi:cli/terminal-input@0.2.2
wasi:cli/terminal-output@0.2.2
wasi:cli/terminal-stdin@0.2.2
wasi:cli/terminal-stdout@0.2.2
wasi:cli/terminal-stderr@0.2.2
wasi:clocks/monotonic-clock@0.2.2
wasi:clocks/wall-clock@0.2.2
wasi:filesystem/types@0.2.2
wasi:filesystem/preopens@0.2.2
wasi:random/random@0.2.2
wasi:http/types@0.2.2
wasi:http/outgoing-handler@0.2.2
組み込むための手順
以下の手順でWasmコンポーネントを、Spinコンポーネントに組み込みます:
- Wasmコンポーネントを手元に用意します
- 用意したWasmコンポーネントをSpinコンポーネントの依存関係に追加します
- 依存するWasmコンポーネントを利用するためのコードを、それぞれのSpinコンポーネントに向けて生成します
- 生成したコードを利用してSpinコンポーネントを実装します
Spin v3.0のアナウンスによると将来的にはWargレジストリーに公開されているWasmパッケージを利用できるようになるようです。記事執筆時点(2024年12月半ば)では、wa.devに公開されたWasmパッケージを利用している場合、Spinアプリの起動ができませんでした。またデモをみると、Spinの開発元であるFermyonがパッケージレジストリーを用意しているようですが、こちらにWasmパッケージを登録することもできませんでした。差し当たり、手元にWasmファイルを用意して作業をすることになりそうです。
なお、wa.devなどからWasmパッケージをダウンロードするには、wkg get
コマンドを利用します。wkg
コマンドはcargo install
コマンドでインストールできます:
% cargo install wkg
spin deps
プラグイン
WasmコンポーネントをSpinの依存関係に追加するには、spin-deps
プラグインを利用します。これのプラグインをインストールすることで、spin
コマンドにdeps
サブコマンドが追加されます。次のようにGitHubのURLを指定してインストールします:
% spin plugins install --url https://github.com/fermyon/spin-deps-plugin/releases/download/canary/spin-deps.json -y
例:Advent of Spin 2024 Challenge 2
Advent of Spinで出題された問題を例に、Wasmコンポーネントの組み込みを解説します。問題では、次のようなWeb APIをSpinコンポーネントとして作成します:
-
/api/naughty-or-nice/:name
がエンドポイントのURLです -
:name
の部分は任意の値が指定されます- 例:
/api/naughty-or-nice/John%20Doe
- パーセントエンコーディングされた値が渡されます
- 例:
- APIは次のようなJSONを返します:
-
name
属性に上記の:name
の値をデコードしたものを記述します -
score
属性の値は0より大きく、100未満の整数値が記述されます -
scoer
属性の算出方法に指定はありません
-
{
name: "John Doe",
score: 30
}
:name
の値から上記のようなJSONとしてにシリアライズされるデータを返す部分を、独立したWasmコンポーネントとして作成します。
インターフェースの定義
まずはWebAssembly Ierface Type(WIT)でインターフェースを定義します。次のように定義しました:
TypeScriptでのインターフェース実装
TypeScriptでインターフェースを実装しました。WITのinterface
は、TypeScriptのオブジェクトとして実装します:
上記の実装からWasmコンポーネントを作成します。作成にはjco
コマンドを利用します。
# TypeScriptをトランスパイルして、JavaScriptを作成します
% tsc -t esnext naughty-or-nice.ts
# JavaScriptからWasmコンポーネントを作成します
% jco componentize #
-n chikoski:advent-of-spin/naughty-or-nice-calculator # ワールド名
-w wit/world.wit # ワールドが定義されているWITファイル
-o naughty-or-nice.wasm # 作成するWasmファイル名
impl/naughty-or-nice.js # 元となるJavaScriptファイル
OK Successfully written naughty-or-nice.wasm.
Spinコンポーネント用プロジェクトの作成
すでに作成されているSpinアプリに、Spinコンポーネントとトリガーを追加します。
% spin add naughty-and-nice
Pick a template to start your component with: http-rust (HTTP request handler using Rust)
Description:
HTTP path: /api/naughty-and-nice/...
追加したらspin.toml
を編集し、wasm32-wasip1
をターゲットにビルドするよう設定を変更します:
依存関係の追加
作成したSpinコンポーネントの依存関係に、TypeScriptから作成したnaughty-or-nice.wasm
を追加します。依存関係に追加することで、TypeScriptで実装したビジネスロジックをSpinコンポーネントから利用できるようになります。
依存関係の追加には、spin deps
コマンドを利用します。CLIが提示する選択肢から適切なものを選んでいくことで、依存関係への追加が行えます:
% spin deps add /path/to/naughty-or-nice.wasm
Select packages to import (use space to select, enter to confirm): chikoski:advent-of-spin@0.2.0 # 依存関係に追加するWITパッケージのIDを指定します
Select one or all interfaces to import from package 'chikoski:advent-of-spin@0Select one or all interfaces to import from package 'chikoski:advent-of-spin@0.2.0': naughty-or-nice-calculatorable # 利用するインターフェースを指定します
Select a component to add the dependency to: naughty-or-nice # Spinコンポーネントを選びます
コンポーネントを利用するためのラッパーコードの生成
Wasmコンポーネントの機能を利用するためには、上記で指定したWasmコンポーネントを利用するためのラッパーコードを生成も必要です。生成はspin deps generate-bindings
コマンドを利用します:
% spin deps generate-bindings #
-L rust # Rust向けのラッパーコードを生成します
-o naughty-or-nice/src/bindings # 生成したコードを保存するフォルダーを指定します
-c naughty-or-nice # Spinコンポーネント名を指定します
生成したラッパーコードは、次のように利用できます。26行目で呼び出しているcalculate()
関数は、TypeScriptで実装したWasmコンポーネントに定義されています:
ビルドと実行
Spinコンポーネントのビルドと、Spinアプリの実行は通常通り行えます:
% spin build
# ビルドメッセージが表示されますが、省略します
% spin up
# 起動メッセージが表示されますが、省略します
naughty-or-nice: http://127.0.0.1:3000/api/naughty-or-nice (wildcard)
インターフェースのバージョンを更新するには
執筆時では、spin.toml
から依存関係を手動で削除し、あらためて依存関係を追加することになるようです。依存するWasmコンポーネントは、次のように記述されています:
まとめと感想
- Spin v3.0以降では、独立したWasmコンポーネントで実装したビジネスロジックをSpinコンポーネントに組み込むことができます
- 組み込みには
spin-deps
プラグインを利用します - wa.devのようなコンポーネントレポジトリーを、早く利用できるようになることを期待しています
ビジネスロジックを独立したコンポーネントとして実装できるのは、責任分界点の設定や、試験の面などで有利に働くことも多いのではないかな?というような想像をしています。
Discussion
spin、めっちゃよくできていますね〜
ところで message 内のリンク先のドメインが
localhost:8000
になってしまっていそうです 👀ありがとうございます!修正しておきました。