見出し画像

wasm-bindgen を利用して Rust な Cloudflare Workers で D1 を利用する

Cloudflare Workers は Cloudflare の Edge Network 上で動くサーバーレス実行環境です(似たものとして AWS の Lambda@Edge が挙げられます)。Cloudflare Workers では D1 という SQLite ベースのリレーショナルデータベースが利用できます。D1 はまだ Alpha 段階であるため、本番環境での利用は避けるべきですが、今後 Edge で構築・完結するアプリケーションの幅が広がっていくのではないかと思います。Cloudflare Workers や D1 の詳細や利用例などは日本語でも様々な例が上がっていますので、そちらを参照してください。

さて、Cloudflare Workers では WebAssembly がサポートされているため、workers-rs を利用して Rust でコードを書くことができます。しかしながら、workers-rs では D1 のクライアントがサポートされておらず、D1 のドキュメントにも JavaScript のサンプルしか掲載されていません。 これについては Feature Request として issue も上がっており、また Pull Request まで作成されている状況なので、近い将来に利用できるようになると思います。ただ、現時点の workers-rs でも Rust な Cloudflare Workers のコードでも D1 を利用できたので紹介したいと思います。

参考にしたのは上記の issue にあるこちらのコメントで、JavaScript から D1 に対してクエリを実行する関数を作成し、wasm-bindgen を利用して Rust からその関数を呼び出せば Rust な Cloudflare Workers のコードでも D1 を利用できるとのことです。これを試したのがこちらのレポジトリになります。

Rust 側のコードは以下のようになっていて、冒頭に wasm_bindgen を利用して、JS 側のコードに実装がある fetch_customers という関数を Rust 側で利用できるようにしています。main 関数ではルーティング処理を記載しています。"GET /customers/:customer_id" といったリクエストを受けるとfetch_customers 関数を利用してパスパラメータ customer_id に一致するレコードを Customers テーブルからクエリして、そのデータを返却するといったコードになっています。

use worker::{
    wasm_bindgen::{prelude::wasm_bindgen, JsValue},
    *,
};

#[wasm_bindgen(module = "/js/d1.js")]
extern "C" {
    async fn fetch_customers(env: &Env, query: &str, customer_id: &str) -> JsValue;
}

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
    Router::new()
        .get_async("/customers/:customer_id", |_, ctx| async move {
            if let Some(customer_id) = ctx.param("customer_id") {
                return match fetch_customers(
                    &ctx.env,
                    "SELECT * FROM Customers WHERE CustomerID = ?",
                    customer_id,
                )
                .await
                .as_string()
                {
                    Some(result) => Response::ok(result),
                    None => Response::error("Internal Server Error", 500),
                };
            }

            Response::error("Bad Request", 400)
        })
        .run(req, env)
        .await
}

JS 側のコードは下記のようになっていて、Workers 上の環境変数 "DB" にバインディングされた D1 クライアントを利用して D1 に対してクエリしています。

export const fetch_customers = async (env, query, customer_id) => {
	const { results } = await env.DB.prepare(query).bind(customer_id).all();
	return JSON.stringify(results);
};

例えば下記の様な SQL クエリをもとに D1 を初期化した状態で "GET /customers/:1" といったリクエストを Cloudflare Workers に投げると次の画像のように期待したレスポンスを得ることができます。

DROP TABLE IF EXISTS Customers;
CREATE TABLE Customers (CustomerID INT, CompanyName TEXT, ContactName TEXT, PRIMARY KEY (`CustomerID`));
INSERT INTO Customers (CustomerID, CompanyName, ContactName) VALUES (1, 'Alfreds Futterkiste', 'Maria Anders'), (4, 'Around the Horn', 'Thomas Hardy'), (11, 'Bs Beverages', 'Victoria Ashworth'), (13, 'Bs Beverages', 'Random Name');
Cloudflare Workers からのレスポンス

このように、wasm-bindgen を利用することで Rust な Cloudflare Workers でも D1 を利用することができました。JavaScript のコードを書く必要があるので workers-rs の目指す 100% Rust なコードではないので、上で上げた Pull Request が早くマージされることを期待しています。


この記事が気に入ったらサポートをしてみませんか?