RustによるマクロサービスフレームワークSpin入門
この記事はAdvent of Spin 2023というオンラインチャレンジに参加した結果してSpinに入門した結果のまとめとなっています。学んだことのまとめですが、Spin入門としても利用いただけるかなとも思っています。
TL;DR
- マクロサービスフレームワークのSpinをつかって、Rustでマイクロサービスを作ります。
- JSONのやり取りや、key-valueストアの利用、ルーティングがカバーされています。
- Hurlというツールを使ってテストファースト開発します。
Spinとは?
Spinとはマイクロサービスを作成するためのフレームワークで次の特徴があります。
- イベント駆動型のフレームワーク
- HTTP(S)とRedisにデフォルトで対応
- いくつかのストレージに標準で対応
- イベントハンドラーはWasmコンポーネントとして実装される
- WASI preview 2に対応
- 開発元が配布しているコンポーネントを、自分のアプリに組み込める
- Rustだけでなく、GoやJavaScriptなどでもハンドラーを記述できる
コンポーネントモデルを早速製品に取り込んでいるというところが、最も大きなアピールポイントでした。
事前準備
次のものを準備しました。
準備するもの | 説明 |
---|---|
Spin | サーバーレス webアプリフレームワークです。 |
Hurl | cURL的なツール。テキストファイルに書かれたシナリオにそってアクセスし、その結果をテストすることができます。 |
spin cloudのアカウント | 必須ではありません。spinで作成したプロジェクトをデプロイする先です。無料でアカウント作成できます。 |
Rustの開発環境 | 必須ではありません。JSやGoなどでも開発できるようです。 |
wasm32-wasiのターゲット | Rustで開発する場合には必要です。 |
Spinのインストール
こちらにインストール方法が解説されています。macOSではHomebrewを使ってインストールできます。
% brew tap fermyon/tap
% brew install fermyon/tap/spin
XCodeのバージョンがチェックされます。私の場合は、古すぎてアップデートを求められました。このアップデートが一番時間がかかりました。
Hurl
% brew install hurl
Spin Cloudへのサインアップとサインイン
GitHubアカウントでサインアップできます。無料です。カードの登録もいらないので、気軽でした。
サインアップ後、CLIからサインインします。以下のコマンド実行後、指示されるアドレスへブラウザーでアクセスすることでサインインできます。
% spin login
wasm32-wasiのターゲットへの追加
次のようにrustup
コマンドを使って追加できます。
% rustup target add wasm32-wasi
hello-world
「Spinについて全く知らない人は、一通り知ってからやるといいと思う」という趣旨の説明があったので、まずはhello-worldを通してSpinに慣れて行きます。
まずspin new
コマンドで、アプリを作成します。
% spin new hello-world-spin
コマンドを実行すると、下記のような画面が表示されます。ここで作成するアプリのテンプレートを選びます。矢印キーの上下で>
を動かしてテンプレートを選んだら、Enterキーを押して決定します。
Pick a template to start your application with:
> http-c (HTTP request handler using C and the Zig toolchain)
http-empty (HTTP application with no components)
http-go (HTTP request handler using (Tiny)Go)
http-grain (HTTP request handler using Grain)
http-js (HTTP request handler using Javascript)
http-php (HTTP request handler using PHP)
http-py (HTTP request handler using Python)
http-rust (HTTP request handler using Rust)
http-swift (HTTP request handler using SwiftWasm)
http-ts (HTTP request handler using Typescript)
http-zig (HTTP request handler using Zig)
redirect (Redirects a HTTP route)
redis-go (Redis message handler using (Tiny)Go)
redis-rust (Redis message handler using Rust)
static-fileserver (Serves static files from an asset directory)
私はRustを使うので、http-rustを選びました。説明と、配置するパスを入力するとアプリが作成されます。
Description: hello world
HTTP path: /...
作製されたフォルダへ移動して、ビルドします。spin build
コマンドでアプリがビルドされます。
% cd hello-world-spin
% spin build
ビルドできることを確認したら、spin up
コマンドを実行してアプリを起動します。下記の例では、http://127.0.0.1:3000/でアプリを起動します。
% spin up
Logging component stdio to ".spin/logs/"
Serving http://127.0.0.1:3000
Available Routes:
hello-world-spin: http://127.0.0.1:3000 (wildcard)
http://127.0.0.1:3000/へブラウザーでアクセスし、次のような画面が表示されれば、アプリが起動しています。
作成されたアプリのフォルダー構成
作成したアプリのフォルダー構成は、次のようになっています。ライブラリーパッケージに、spin用の設定が書かれたspin.tomlが追加されています。
.
├── Cargo.lock
├── Cargo.toml
├── spin.toml
├── src
└── lib.rs
Cargo.tomlはWasmをビルドするための設定がlib
セクションにあるのと、spinがdependencies
セクションに書かれているくらいで、大きな変更は加えられていません。
name = "hello-world-spin"
authors = ["Author Name <author-name@example.com>"]
description = "hello world"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = [ "cdylib" ]
[dependencies]
anyhow = "1"
http = "0.2"
spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v2.0.1" }
Hurlを使ってみる
インストールしたHurlはテキストファイルを読んで、そこに書かれているシナリオ通りにHTTP/HTTPS通信を行います。作成したアプリの実行結果を確認するシナリオを次のようにetc
フォルダー内に記述します。わかりやすさのために、hurlに与えるシナリオは.hurl
という拡張子をつけることとしました。
% mkdir etc
% echo "GET http://127.0.0.1:3000/" > etc/get.hurl
作成したシナリオを実行します。標準では、結果を標準出力に出力します。
% hurl etc/get.hurl
Handling request to Some(HeaderValue { inner: String("http://localhost:3000/") })
Hello, Fermyon%
hurlはテストツールとしての側面もあります。--test
オプションをつけると、テスト結果らしく表示されます。
hurl --test etc/get.txt
etc/get.txt: Running [1/1]
Handling request to Some(HeaderValue { inner: String("http://localhost:3000/") })
etc/get.txt: Success (1 request(s) in 3 ms)
--------------------------------------------------------------------------------
Executed files: 1
Succeeded files: 1 (100.0%)
Failed files: 0 (0.0%)
Duration: 5 ms
テストというからには、シナリオに検査する条件をかけます。省略された場合は、ステータスコードが200だと成功とみなされるようです。
先のシナリオを次のように変更します。これでhttp://localhost:3000がステータスコード301が返えすことを検査できます。
GET http://localhost:3000/
HTTP 301
検査結果は次のようになります。期待した値と異なるステータスコードが返ってきたことがわかります。
% hurl --test etc/get.txt
etc/get.txt: Running [1/1]
Handling request to Some(HeaderValue { inner: String("http://localhost:3000/") })
error: Assert status code
--> etc/get.txt:2:6
|
2 | HTTP 301
| ^^^ actual value is <200>
|
etc/get.txt: Failure (1 request(s) in 5 ms)
--------------------------------------------------------------------------------
Executed files: 1
Succeeded files: 0 (0.0%)
Failed files: 1 (100.0%)
Duration: 8 ms
先の修正は、エラー状態となることを意図していました。テストが通るよう、次のようにシナリオを修正します。今回は明示的に期待するステータスコードを記述しました。
GET http://localhost:3000/
HTTP 200
レスポンスに対するアサーションを書く
ステータスコード以外に、レスポンスへの検査をアサーションという形でかけます。シナリオにAsserts
セクションを追加して、その中にアサーションを列挙します。例えばレスポンスヘッダーのContent-Type
の値がtext/plain
でなければならないことは、次のように記述できます。
GET http://localhost:3000/
HTTP 200
[Asserts]
header "Content-Type" == "text/plain"
レスポンスボディに対するアサーション
レスポンスヘッダーだけでなく、レスポンスボディに対するアサーションも記述できます。レスポンスボディに"Hello"という文字列が含まれていることの検査は、次のようにcontains
という述語を使って記述できます。
GET http://localhost:3000/
HTTP 200
[Asserts]
header "Content-Type" == "text/plain"
body contains "Hello"
アサーションの構造
アサーションはクエリ、述語、期待する値の3つ組で構成されています。上述の例は次のような構造をしています。
クエリ | 述語 | 期待する値 |
---|---|---|
header "Content-Type" |
== |
"text/plain" |
body |
contains |
"Hello" |
利用できるクエリは、次の12種類です。
- status
- header
- url
- cookie
- body
- bytes
- xpath
- jsonpath
- regex
- variable
- duration
- certificate
hello-worldアプリにルーティングを追加する
Spinのルーティングは2段階に分かれます。
- 呼び出すコンポーネントの決定
- コンポーネント内でのルーティング
1のことをSpinは「トリガー」と呼んでいます。トリガーはspin.tomlの記述に基づいて行われます。
2は自分で実装することになるのですが、Rust向けのSDKに用意されているRouterを利用すると良さそうです。
トリガー
spin.tomlには次のような記述が何個もあらわれます。これがトリガーの設定です。この記述は「HTTPでのアクセスで、パスが/
で始まる場合は、hello-world-spin
というコンポーネントを利用する」と解釈されます。
[[trigger.http]]
route = "/..."
component = "hello-world-spin"
上記の記述は次のような構造を持っています。
記述 | 解釈 | 説明 |
---|---|---|
[[trigger.http]] |
HTTPでのアクセスで | イベントの種類 |
route = "/..." |
パスが/ で始まる場合は、 |
ルート |
component = "hello-world-spin" |
hello-world-spin というコンポーネントを利用する |
コンポーネントの指定 |
Spinが標準で対応しているイベントは、次の2種類です。Redisアクセスの場合は[[trigger.redis]]
と記述します。
- HTTPアクセス
- Redisアクセス
route
属性の値にある...
はワイルドカードです。次の3つ全てがマッチします。
/
/hello-world
/hello/world
コンポーネント内でのルーティング
トリガーの設定では、パターンを使ったパスやクエリストリングからパラメーターの切り出しや、HTTPメソッドに応じたコンポーネントの処理の切り替えといった細かなルールを記述できません。これらはコンポーネントの責任とされています。つまりいわゆるルーティングは、コンポーネント内で実装することとなります。
Rust向けSDKにはRouterの実装が提供されているため、これを使うと細かい文字列操作をしなくても済むので良いでしょう。おおまかな説明はドキュメントのRouting in a Componentで解説されています。細かいAPIドキュメントを見つけることができませんでしたが、ソースコードと内部で利用しているクレートが参考になるように思いました。
hello-worldへのルーティングの追加
コンポーネントにルーティングを追加して、次の振る舞いを実装します。
GET http://localhost:3000/world
HTTP 200
[Asserts]
body == "Hello, world"
GET http://localhost:3000/
HTTP 200
[Asserts]
body == "Hello, Spin"
lib.rs
を次のように変更しました。api
モジュールを追加してAPIを実装した点と、handle_hello_world_spin
関数にRouter
を追加して、APIとパスとの関連付けを行った点の2点が変更点です。
use spin_sdk::http::{Request, Response, Router};
use spin_sdk::http_component;
/// A simple Spin HTTP component.
#[http_component]
fn handle_hello_world_spin(req: Request) -> Response {
let mut router = Router::new();
router.get("/:name", api::greet);
router.handle(req)
}
mod api {
use spin_sdk::http::{IntoResponse, Params, Request};
pub fn greet(_: Request, params: Params) -> anyhow::Result<impl IntoResponse> {
let name = params.get("name").unwrap_or("Spin");
let greetings = format!("Hello, {}", name);
Ok(http::Response::builder()
.status(200)
.header("content-type", "text/plain")
.body(greetings)?)
}
}
変更適用後、テストを実行すると、次のようなエラーが発生します。
|
2 | HTTP 200
| ^^^ actual value is <404>
|
これは次の'/'に対するテスト結果でした。
GET http://localhost:3000/
HTTP 200
[Asserts]
body == "Hello, Spin"
name
パラメーターが指定されていない場合のパターンを認識できず、ルートが決められなかったため404が返ってきているようです。そこで'/'専用のパターンを次のように追加したところ、テストは全て通るようになります。
fn handle_hello_world_spin(req: Request) -> Response {
let mut router = Router::new();
router.get("/", api::greet);
router.get("/:name", api::greet);
router.handle(req)
}
Advent of Spin 2023 Challenge #1
長いhello-worldを終え、ようやく1番目の問題に挑戦します。別のアプリを作成します。
% spin new advent-of-spin2023-challenge01
作成されたspin.tomlのトリガーは以下のようになっています。route
が/data
となっているのは、問題の仕様からです。次の節で述べますが、/data
に実装するエンドポイントが配置されているように仕様で求められています。
[[trigger.http]]
route = "/data"
component = "advent-of-spin2023-challenge01"
なお、advent-of-spin-2023-01
という名前でアプリを作成しようとすると、エラーが発生しました。-
で区切られた各セグメントは文字で始まらなければならないルールだそうです。adevent-of-spin-y2023-challenge01
といった名前なら問題なさそうです。
% spin new advent-of-spin-2023-01
error: Invalid value "advent-of-spin-2023-01" for '<NAME>': Each segment of the name must start with a letter. '2023', '01' do not start with a letter
仕様
詳細はこちらに記載されていますが、まとめると次のようになろうかと思います。テストを見た方が簡潔でわかりやすいかもしれません。
-
/index.html
がフロントエンド - フロントエンドから参照されるエンドポイントが
/data
- GETとPOSTを受け付ける
- POSTの場合:受け付けたJSONに記述されている
value
の値で、保存している値を更新する - GETの場合:保持している値をJSON形式で出力する
- クエリストリングの値をキーとして値を保存する
- POSTの場合:受け付けたJSONに記述されている
- 出力のcontent-typeはapplication/json
- GETとPOSTを受け付ける
静的ファイル配信用のコンポーネント追加
静的ファイルの配信をするために、静的ファイルを配信するためのコンポーネントを追加します。コンポーネントはSpinの開発元が配布しているものを利用します。
Spinでは、1つのアプリに複数のコンポーネントを利用することができます。spin add
コマンドで、アプリへのコンポーネントを追加できます。利用するspin-fileserverコンポーネントは、用意されているテンプレートを利用して追加すると設定が簡単に行えます。
% cd advent-of-spin2023-challenge01
% spin add -t static-fileserver
上述のコマンドを実行してコンポーネントを追加した後のspin.tomlは、次のようになっています。[[trigger.http]]
が2つあることに注目してください。2つ目のものが、静的ファイル配信用のコンポーネントに関する設定です。/data
以外へのアクセスはstatic-file
コンポーネントが処理するように設定しています。
spin_manifest_version = 2
[application]
name = "advent-of-spin2023-challenge01"
version = "0.1.0"
authors = ["Author Name <author-name@example.com>"]
description = "Advent of Spin 2023 Challenge 01"
[[trigger.http]]
route = "/data"
component = "advent-of-spin2023-challenge01"
[component.advent-of-spin2023-challenge01]
source = "target/wasm32-wasi/release/advent_of_spin2023_challenge01.wasm"
allowed_outbound_hosts = []
[component.advent-of-spin2023-challenge01.build]
command = "cargo build --target wasm32-wasi --release"
watch = ["src/**/*.rs", "Cargo.toml"]
[[trigger.http]]
route = "/..."
component = "static-file"
[component.static-file]
source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.1.0/spin_statics.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" }
files = [{ source = "assets", destination = "/" }]
static-file
コンポーネントの自体の設定が、[component.static-file]
セクションに記述されています。fieles
属性に、ファイルシステム上のパスとURL上のパスとの対応関係が記述できます。今回はすべてのファイルがassets
フォルダに入っていることにしました。
source
属性で、アプリで利用するWasmコンポーネントの位置を指定します。GitHubでホストされているspin_static_fs.wasm
を利用します。このままでも動作しますが、最新版のv0.2.1を利用するように変更しました。digest
の値はリリースページにあるchecksums-v0.2.1txtに書かれています。
[component.static-file]
source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.2.1/spin_static_fs.wasm", digest = "sha256:5f05b15f0f7cd353d390bc5ebffec7fe25c6a6d7a05b9366c86dcb1a346e9f0f" }
files = [{ source = "assets", destination = "/" }]
静的ファイルの配置
assets
フォルダを作成し、設定した通り静的ファイルを配信できるようにします。空のindex.htmlを配置しました。
% mkdir assets
% touch assets/index.html
テストの設定
Advent of Spinのレポジトリ公開されているhurlファイルをもとに、テストを作成します。ファイルをそのまま利用してもよかったのですが、結果の見やすさのために、テストを3つのファイルに分割しました。
% mkdir tests
% cat > tests/test01.hurl # Test 1 の内容を test01.hurl にコピペ
% cat > tests/test01.hurl # Test 2 の内容を test01.hurl にコピペ
% cat > tests/test01.hurl # Test 3 の内容を test01.hurl にコピペ
ビルドと起動、そしてテスト。
とりあえずビルドして起動します。
(省略)
Serving http://127.0.0.1:3000
Available Routes:
advent-of-spin2023-challenge01: http://127.0.0.1:3000/data
static-file: http://127.0.0.1:3000 (wildcard)
その後、テストを実行します。index.htmlの有無を確かめるテスト(test01)以外は全て失敗しています。つまり、satic-file
コンポーネントは意図通り動作していることがわかりました。
% hurl tests/* --test
tests/test01.hurl: Running [1/3]
tests/test01.hurl: Success (1 request(s) in 53 ms)
tests/test02.hurl: Running [2/3]
Handling request to Some(HeaderValue { inner: String("http://localhost:3000/data?advent") })
error: Assert status code
--> tests/test02.hurl:7:6
|
7 | HTTP 201
| ^^^ actual value is <200>
|
tests/test02.hurl: Failure (1 request(s) in 5 ms)
tests/test03.hurl: Running [3/3]
Handling request to Some(HeaderValue { inner: String("http://localhost:3000/data?advent") })
error: Assert failure
--> tests/test03.hurl:6:0
|
6 | header "Content-Type" == "application/json"
| actual: string <text/plain>
| expected: string <application/json>
|
error: Invalid JSON
--> tests/test03.hurl:7:1
|
7 | jsonpath "$.value" == "of Spin"
| ^^^^^^^^^^^^^^^^^^ the HTTP response is not a valid JSON
|
tests/test03.hurl: Failure (1 request(s) in 1 ms)
--------------------------------------------------------------------------------
Executed files: 3
Succeeded files: 1 (33.3%)
Failed files: 2 (66.7%)
Duration: 62 ms
コンポーネント内ルーティングの設定
src/lib.rs
を開き、コンポーネント内ルーティングの設定をします。Router
オブジェクトにルールを追加していけば良いのですが、get
メソッドやpost
メソッドに指定するパスは絶対パスである必要があります。私は次のようにルーティングの設定をしました。
use spin_sdk::http::{Request, Response, Router};
use spin_sdk::http_component;
use url::Url;
/// A simple Spin HTTP component.
#[http_component]
fn handle_advent_of_spin2023_challenge01(req: Request) -> Response {
let mut router = Router::new();
router.get("/data", api::show_whishlist);
router.post("/data", api::update_whishlist);
router.handle(req)
}
mod api {
pub fn update_whishlist(_: Request, _: Params) -> anyhow::Result<impl IntoResponse> {
let json_string = String::new();
Ok(Response::builder()
.status(201)
.header("content-type", "application/json")
.body(json_string)
.build())
}
pub fn show_whishlist(_: Request, _: Params) -> anyhow::Result<impl IntoResponse> {
let json_string = String::new();
Ok(Response::builder()
.status(200)
.header("content-type", "application/json")
.body(json_string)
.build())
}
}
POSTされたデータによるデータの更新
仕様からPOSTされたデータを保存して置く必要があることがわかります。Spinは標準で次のストレージを利用できます。なおMySQLはSpinに含まれません。別途サービスを設定し、起動する必要があります。またMySQL以外のRDBMSも利用できますが、操作するライブラリーがwasm32-wasiにビルドできることが必要です。
今回は簡単に使用できるkey-valueストア(KVS)を利用することにしました。
key-valueストアの設定
KVSを利用するためには、spin.tomlのcomponent設定に、次のように利用するKVSの名前を登録する必要があります。今回はデフォルトのKVSを表す"default"
を登録しました。
[component.advent-of-spin2023-challenge01]
source = "target/wasm32-wasi/release/advent_of_spin2023_challenge01.wasm"
allowed_outbound_hosts = []
key_value_stores = ["default"]
KVSに保存できるオブジェクトは、Serde::Deserialize
を実装している必要があります。またJSON形式のデータを扱うことを考えると、Serdeとserde_jsonが必要です。次のようにcargo add
コマンドで依存するクレートに追加しておきます。なおSerdeはderive
マクロと共に利用するので、derive
フィーチャーを有効にしておきます。
% cargo add serde -F derive
% cargo add serde_json
POSTのハンドラーの実装
POSTハンドラーのやることは、次の3つです。
- POSTされたデータをJSONとして構文解析する
- 解析されたデータのvalue属性の値で、保存されている値を更新する
- 更新されたデータを
{ value: 値 }
の形式でJSONとして返す
まずやりとりするデータ構造を作ります。JSONに変換できるように、derive
マクロを使ってSerde::Deserialize
とSerde::Serialize
を実装します。次のスニペットでは2つのトレイトに加えて、デバッグ用にDebug
トレイトも実装しています。また、いくつかのメソッドも実装しています。
mod api {
// 中略
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct Whishlist {
value: String,
}
impl Whishlist {
fn new(value: String) -> Whishlist {
Whishlist { value }
}
fn empty() -> Whishlist {
Whishlist::new(String::new())
}
}
// 中略
}
あとは上記の手順をupdate_whitelist
関数に実装します。設定したdefault
のKVSはStore::open_default
関数が返すハンドル経由で利用できるようになります。get
メソッドで保存されている値を取得できます。次のスニペットが実装した例です。
mod api {
// 中略
pub fn update_whishlist(req: Request, _: Params) -> anyhow::Result<impl IntoResponse> {
let store = Store::open_default()?;
let posted: Whishlist = serde_json::from_slice(req.body())?;
let key = req.query();
store.set_json(key, &posted)?;
let json_string = serde_json::to_string(&posted)?;
Ok(Response::builder()
.status(201)
.header("content-type", "application/json")
.body(json_string)
.build())
}
// 中略
}
上記を実装した上でSpinを再起動すると、2つ目のテストまでパスするようになります。
なお、Response::builder
はHTTPレスポンスを作成するためのビルダーオブジェクトを返します。header
メソッドを呼ぶことで、HTTPヘッダーを変更できます。同様にbody
メソッドでHTTPボディを設定できます。設定が終わったら、build
メソッドを呼んで設定済みのResponse
オブジェクトを取得します。
GETハンドラーの実装
POSTハンドラーと同様にGETハンドラーを実装します。ハンドラーのやることは、次の3つです。
- KVSから保存されているwatchlistを取得する
- 取得したwatchlistを、期待される形のJSONに変形する
- 変形したJSONをレスポンスとして返す
上記の3つを順に実装すれば、3つ目のテストもパスできます。実装した例が次のスニペットです。
mod api {
// 中略
pub fn show_whishlist(req: Request, _: Params) -> anyhow::Result<impl IntoResponse> {
let store = Store::open_default()?;
let key = req.query();
let whilshlist = store.get_json(key)?.unwrap_or(Whishlist::empty());
let json_string = serde_json::to_string(&whilshlist)?;
Ok(Response::builder()
.status(200)
.header("content-type", "application/json")
.body(json_string)
.build())
}
// 中略
}
提出
spin deploy
コマンドで、作成したアプリをFremyonのCloudサービスに配置できます。配置したらここに記載されている通りにhurl
コマンドを実行して提出完了です。お疲れ様でした。
% spin deploy
Uploading advent-of-spin2023-challenge01 version 0.1.0 to Fermyon Cloud...
Deploying...
Waiting for application to become ready................. ready
Available Routes:
advent-of-spin2023-challenge01: https://advent-of-spin2023-challenge01-somewhere-in.fermyon.app/data
static-file: https://advent-of-spin2023-challenge01-somewhere-in.fermyon.app (wildcard)
まとめと感想
環境を作るところから始めて、hello-world、複数のコンポーネントを組み合わせてアプリを作るところまで一通り体験できました。触れた内容は次のようになるかと思います:
- Spinの導入
- Hurlを使ったテストファースト開発
- ルーティング
- 複数コンポーネントを組み合わせた開発
- KVSの利用
組み込みのルーターが素朴で、やらなきゃいけないことが多いなあという印象を受けました。ただ、やり取りはされるデーターは全てJSONなりgRPCになっているという前提だと、これでいいのかもしれません。データシリアライゼーションライブラリーが組み込み出ないのも、開発者の選択肢を狭めたくないということなのかもしれません。
また大きなコンポーネントを作るというよりは、小さいコンポーネントを組み合わせて大きな機能を作るという方針のようにも見えました。その方針にWasmコンポーネントは悪くない選択なようにも感じています。
公式サイトのドキュメントが過不足はないんだけれど、分散していて手厚い感じがしないようにも思いました。せめてcrates.ioのページへのリンクや、doc.rsにあるSDKのドキュメントへのリンクがあれば、いろいろ探す手間が省けてよかったようにもおもいます。公式サイトの色使いやフォントが私の目には優しくなかくドキュメントを読み進めるのが難しく感じたのも、欲しい情報が探しにくいと感じた一因かもしれません。
アプリをOCIレジストリー経由で公開できるspin registory
というコマンドを見つけたので、これを使って自作のアプリをOCIレジストリに公開するのが次のアクションになるかなと考えています。またアプリをFermyon以外のクラウドサービスに配置する方法についても興味があります。あとは自作のコンポーネントをうまく使って再利用性を上げるシナリオが見つかればいいなとも思っています。
Discussion
RustでWebアプリケーションを作るのが当たり前な時代がいつかくるかもしれないですね。
こちらの記事を拝読して公式の言う「for building WebAssembly microservices 」が腑に落ちました。 One component = One microservice という考え方っぽいですね。
究極はOne component = One endpointの粒度でも構成できそうですが、そうせずに「まずはざっくりパス単位で分ける」「そしてComponent内で更にRouterを使う」という設計にしているのは過去にこういうことに悩んだのでベターな方法だなとおもって一人納得しました。