背景
Lambda関数をRustで実装しており、InvokeAPIで叩きます。
フロントエンド側での実装と並行して、Lambda関数自体を同時に実装しており、
AWS上へのデプロイ前にローカルでフロントエンドと接続して動作検証をしたいです。
また、上記をできるだけ、手っ取り早くやりたいです。
環境
フロントエンド:Next.js(なんでも)
Lambda関数:Rust 1.86
Lambda関数の準備
下記の記事を参考に、RustでLambda関数を実装します。
詳細は割愛しますが、下記のように、Lambda関数(lambda_handler)と、それでラップした具体的な処理(execute)を実装します。
execute内で、実際はLambda関数での行う処理を書きます。
今回は便宜的に、何か処理をした上で、responseを返した前提で、仮のResponseを返しています。
(idをpostして、そのままidを返しているのが、変ですが、例ということで・・・)
#[derive(Debug, Deserialize)]
pub struct LambdaEventPayload {
pub id: String,
}
#[derive(Serialize)]
pub struct Response {
pub id: String,
pub message: String,
}
#[tokio::main]
async fn main() -> Result<(),lambda_runtime::Error> {
lambda_runtime::run(service_fn(lambda_handler)).await;
Ok(())
}
async fn lambda_handler(
event:LambdaEvent<LambdaEventPayload>,
) -> Result<(), lambda_runtime::Error> {
let payload = event.payload;
execute(payload).await?;
Ok(())
}
pub async fn execute(payload: LambdaEventPayload) -> Result<Response, lambda_runtime::Error> {
let aws_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
//実際は、ここで、他のAWSサービスのリソースにアクセスするなど、いろいろな処理を行う。
let record: Response = Response {
id: payload.id.clone(),
message: format!("Hello, {}!", payload.id),
};
Ok(record)
}
(本題)ローカルでの立ち上げ
方針
ここからが本題で、上記のLambda関数をNext.jsからInvokeしたいです。
手段としては、いくつかあります。
- 先にLambda関数をデプロイして、実際にInvokeAPIを叩いて試す
- AWS SAMを使って、LocalでLambda関数を実行可能にする
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/what-is-sam.html - ローカルでexecute部分の処理のみをWebサーバーとして立ち上げる
今回は、
・フロントエンドの実装とLambda関数の実装を並行するため、1はできない
・SAMは、CLIの導入などのセットアップをこれのために、前回初メンバーで行うのは面倒
という点から、3で簡易的に試すこととしました。
もちろん、InvokeAPIをきちんと叩けているかなどは確認できないため、そこまでしたい場合は、1,2で行う必要があります。
あくまでも、簡単にLambda関数の中身の処理を、フロントエンドから叩いて試す際の方法が今回記載する方法になります。
また、Webサーバーは、ローカルでの使えるようにしたいので、テストとして実行することで、立ち上げられるようにします。
actix-web
Rustで書かれたexecuteをWebサーバーで立ち上げるために、RustのWebフレームワークのactix-webを使用します。
https://actix.rs/
これでAPIサーバーを立ち上げ、そこにリクエストすることで、InvokeAPIで行う処理を代替します。
実装
まず、actix-webをCargo.tomlのdependenciesに追加します。
actix-web = "4"
上述のLambda関数と、同じファイル内に、testとして下記を追加します。
portは他と重複しないportを書き込めば良いです。
#[cfg(test)]
mod tests {
use actix_web::{get,post,web, App, HttpResponse, HttpServer, Responder};
use super::*;
#[post("/message")]
async fn get_message(
payload: web::Json<LambdaEventPayload>,
) -> impl Responder {
match execute(payload.into_inner()).await {
Ok(response) => HttpResponse::Ok().json(response),
Err(e) => {
eprintln!("Error: {}", e);
HttpResponse::InternalServerError().json("Internal server error")
}
}
}
#[get("/health")]
async fn health_check() -> impl Responder {
HttpResponse::Ok().json("OK")
}
pub async fn start_test_server() -> std::io::Result<()> {
let port = 18080;
HttpServer::new(|| {
App::new()
.service(get_message)
.service(health_check)
})
.bind(("127.0.0.1", port))?
.run()
.await
}
#[tokio::test]
#[ignore]
async fn start_web_server_test() {
start_test_server().await.unwrap();
}
}
上記の場合、
/messageへのpostと、サーバーが立ち上がっているかの確認用に、/healthへのgetをルートとして用意しています。
検証
rust-analyzerで、run testをするか、
cargo test start_web_server_test -- --ignored
をターミナルで実行して、start_web_server_testを実行することで、サーバーを立ち上げられる。
running 1 testと出てきたら、立ち上がっています。
また、start_web_server_testは上記のように、testが終了しないで起動したままになるので、cargo testで引っかからないように、#[ignore] にしています。
start_web_server_testを実行して、サーバーを立ち上げたら、
http://localhost:18080/health
(portは自身が決めたものに読み替えてください)
にアクセスし、OKが表示されたら、OKです。
これで、/messageへのpostも使えます。
必要に応じて、curlで確認します。
curl -X POST http://localhost:18080/message \
-H "Content-Type: application/json" \
-d '{
"id": "aaa"
}'
レスポンス
{"id":"aaa","message":"Hello, aaa!"}
注意:上記を実行中に、executeやstart_web_server_testのコードを書き換えても、反映されない。一度、実行中のstart_web_server_testを停止して、再度実行する必要がある。(実行時点でコンパイルされているため)
あとは、フロントエンド(Next.jsなど)から、axiosなど好きな形で、
http://localhost:18080/message
へpostすればOKです。
Next.jsでは、本番環境ではInvokeAPIを叩き、ローカルでは上記のローカルサーバーのAPIを叩くように、環境変数で出し分けるなどします。
まとめ
Lambda関数を実装する時点で、Lambda関数のハンドラーと、実際の処理(execute)を切り分けておくことで、こういったローカルサーバーで立ち上げての検証もできています。
前述の記事にあるように、実際の処理を切り分けておくと便利だと実感しました。
また、これだと、InvokeAPIがきちんと叩けているかは、ローカルで確認できないですが、
AWS SDK for JavaScriptを使って、実装するのであれば、よほどそこの実装は問題ないものとして、Sandbox環境などにデプロイした上で確認する形にするのも、セットアップの手間を考えるとありなのではと思いました。