🌀

Wasmコンポーネントの活用:Spin v3.0によるWebアプリ開発

2024/12/16に公開
2

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コンポーネントに組み込みます:

  1. Wasmコンポーネントを手元に用意します
  2. 用意したWasmコンポーネントをSpinコンポーネントの依存関係に追加します
  3. 依存するWasmコンポーネントを利用するためのコードを、それぞれのSpinコンポーネントに向けて生成します
  4. 生成したコードを利用して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は次のようなJSONを返します:
    • name属性に上記の:nameの値をデコードしたものを記述します
    • score属性の値は0より大きく、100未満の整数値が記述されます
    • scoer属性の算出方法に指定はありません
{
    name: "John Doe",
    score: 30
}

:nameの値から上記のようなJSONとしてにシリアライズされるデータを返す部分を、独立したWasmコンポーネントとして作成します。

インターフェースの定義

まずはWebAssembly Ierface Type(WIT)でインターフェースを定義します。次のように定義しました:

https://github.com/chikoski/advent-of-spin/blob/solution-2024/2024/app/lib/naughty-or-nice-calculator/wit/world.wit

TypeScriptでのインターフェース実装

TypeScriptでインターフェースを実装しました。WITのinterfaceは、TypeScriptのオブジェクトとして実装します:

https://github.com/chikoski/advent-of-spin/blob/solution-2024/2024/app/lib/naughty-or-nice-calculator/impl/naughty-or-nice.ts

上記の実装から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をターゲットにビルドするよう設定を変更します:

https://github.com/chikoski/advent-of-spin/blob/solution-2024/2024/app/spin.toml#L34-L40

依存関係の追加

作成した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コンポーネントに定義されています:

https://github.com/chikoski/advent-of-spin/blob/solution-2024/2024/app/naughty-or-nice/src/lib.rs#L22-L27

ビルドと実行

Spinコンポーネントのビルドと、Spinアプリの実行は通常通り行えます:

% spin build
# ビルドメッセージが表示されますが、省略します
% spin up
# 起動メッセージが表示されますが、省略します
  naughty-or-nice: http://127.0.0.1:3000/api/naughty-or-nice (wildcard)

インターフェースのバージョンを更新するには

執筆時では、spin.tomlから依存関係を手動で削除し、あらためて依存関係を追加することになるようです。依存するWasmコンポーネントは、次のように記述されています:

https://github.com/chikoski/advent-of-spin/blob/solution-2024/2024/app/spin.toml#L42-L43

まとめと感想

  • Spin v3.0以降では、独立したWasmコンポーネントで実装したビジネスロジックをSpinコンポーネントに組み込むことができます
  • 組み込みにはspin-depsプラグインを利用します
  • wa.devのようなコンポーネントレポジトリーを、早く利用できるようになることを期待しています

ビジネスロジックを独立したコンポーネントとして実装できるのは、責任分界点の設定や、試験の面などで有利に働くことも多いのではないかな?というような想像をしています。

Discussion

tanishikingtanishiking

spin、めっちゃよくできていますね〜
ところで message 内のリンク先のドメインが localhost:8000 になってしまっていそうです 👀

chikoskichikoski

ありがとうございます!修正しておきました。