Slide 1

Slide 1 text

多観点で比較する Lambdalith vs 単一目的 Lambda 2026/08/28 品川会LT クラスメソッド株式会社 リテールアプリ共創部 戸田 駿太

Slide 2

Slide 2 text

自己紹介 名前: 戸田 駿太 X: @shuntemskills 会社: クラスメソッド株式会社 部署: リテールアプリ共創部 趣味: 料理・キャンプ・サッカー ・バイク・ヨーヨー 2 経歴 2023年12月 技能五輪全国大会ウェブデザイン 2度目の金賞 2024年4月 クラスメソッド新卒入社 2024年9月 技能五輪国際大会 WebTechnologies 日本代表出場 2024年10月 リテールアプリ共創部へ配属 2026年 2026 Japan AWS Jr. Champions に選出 現在 フルスタックエンジニア (インフラ・バックエンド・フロントエンド) として新規開発・追加開発・運用保守でAI駆動開発中

Slide 3

Slide 3 text

この LT で比べる 2 つの構成 3 同じ CDK + TypeScript で作った2つの案件を、構成の違いで見比べます A 単一目的Lambda(API Gateway + 分割) パスごとに Lambda を置き、API Gateway がルー ティング AWS 公式が推奨する形(Well-Architected Serverless Lens) B Lambdalith(1 つの Lambda に集約) 1つの Lambda の中で Hono が全パスをルーティン グ 公式ガイドではアンチパターン扱い。でもコミュニ ティの潮目は変わってきた 観点ごとに「A ではこう / B ではこう / どちらが有利か」を順番に見ていきます

Slide 4

Slide 4 text

構成A: 単一目的 Lambda(API Gateway + パスごと) パスごとに Lambda POST /users GET /users/me Client API Gateway ルーティングはここ GET /users/me/cards DELETE /users/me/cards/{id} … エンドポイント数だけ続く API Gateway の Resource / Method がルーティングし、パスごとに Lambda を置く ハンドラは APIGatewayProxyEvent を受け取る 4

Slide 5

Slide 5 text

構成B: Lambdalith(1 つの Lambda + Hono) Lambda × 1 Hono API Gateway POST /users or GET /users/me Client Lambda GET /users/me/cards DELETE /users/me/cards/:id CloudFront … 全部この中 ルーティングはここ 入口は API Gateway でも CloudFront(Function URL)でもよい。行き先は 1 つの Lambda Lambda の中で Hono が全パスをルーティング 5

Slide 6

Slide 6 text

観点1: コールドスタート・スケール

Slide 7

Slide 7 text

コールドスタートとスケールの特性 7 A 小さい関数がたくさん B 大きい関数が 1 つ バンドルが小さいため1回のコールドスタートは速い でも 関数ごとにコールドスタートが起きる →低トラフィックだと全関数がコールド状態 ルートごとに同時実行数の枠を予約できる (Reserved concurrency) スケール上限(10 秒ごとに 1,000 実行環境)をル ートごとに持てる 関数が大きくなる分、1回のコールドスタートは長い → esbuild + Hono なら軽量なため実害は小さい 1 関数にトラフィックが集中するので コールドスタ ートの頻度は下がる ルート単位でスロットルできない。スケール上限を 全ルートで共有 条件次第 — 急スパイク・ルート別の制御が必要なら A、要件がなければBでいい 出典: Yan Cui「The pros and cons of Lambdalith」(2025) / Ran Isenberg / AWS Lambda quotas

Slide 8

Slide 8 text

観点2: 各種機能をどちらの役割にするか

Slide 9

Slide 9 text

API Gateway の機能を使うか Hono の機能を使うか A 「Lambda を起動せずに」ルート単位で効 かせる B 「Lambda の中で」ルート単位に効かせる ここで弾く ここで弾く Authorizer(Cognito / Lambda / IAM)で起動 前に 401/403 Request Validator(JSON Schema)で起動前に 400 メソッド単位スロットル / 使用量プラン / API Key / キャッシュ Lambda を通さない 直接サービス統合 (DynamoDB / SQS / Step Functions) 弾いたリクエストは Lambda の課金も実行もされ ない ルート別の認証はミドルウェアで できる app.use("/admin/*", jwt()) Zod 検証 / ロール認可 / CORS / エラー整形 / アク セスログ ただし判定は必ず Lambda 起動後に実行 手前で弾くなら CloudFront + WAF / API GW {proxy+} + Authorizerを利用する → ただし全てのAPIに影響 インフラ層で各種設定したいなら A、その要件がなければ B でいい 9

Slide 10

Slide 10 text

観点3: 権限(IAM)

Slide 11

Slide 11 text

IAM ロールの粒度 11 A 関数ごとに最小権限 B 1 つのロールが全ルートの権限を持つ getCardHandler createUserHandler deleteCardHandler handler → DB Secret read + S3 read/write + メール配信 API の Secret read + Cognito AdminCreateUser ... → S3 read のみ → DynamoDB write のみ → DynamoDB delete のみ 1 関数 = 1 ロール。侵害されても影響範囲はそのル ートだけ Well-Architected が推す理由そのもの 細かく調整できる A が有利 最小権限の粒度が「ルート単位」から「API 単位」 に粗くなる → サービス単位の最小権限で十分という説もある

Slide 12

Slide 12 text

観点4: コードの保守性・ローカル実行・テスト

Slide 13

Slide 13 text

ローカル実行とテストのやり方 13 A ハンドラが AWS のイベント形式に依存 B Hono は AWS に依存していない export const handler = async ( event: APIGatewayProxyEvent, // lambda.ts import { handle } from "hono/aws-lambda"; ): Promise => { const body = JSON.parse(event.body ?? "{}"); const cardId = event.pathParameters?.cardId; // ... return { statusCode: 200, body: JSON.stringify(res) }; }; ローカル実行をするには特別な仕組みが必要(SAM local / localstack / 手製イベント JSON など) → 本番環境には関係ない余計な依存と処理が発生 API設計書などは別で作成する必要がある ECS などに移行するには全ハンドラを書き直す必要 がある const app = createApp({ ...deps }); export const handler = handle(app); // index.ts(ローカル) import { serve } from "@hono/node-server"; const app = createApp({ ...deps }); serve({ fetch: app.fetch, port: 8080 }); ローカル実行はHonoの仕組みで完結 ( pnpm dev → localhost:8080 ) その他Honoの素晴らしい機能がそのまま利用可能 → 案件では Zod OpenAPI や Swagger UI 、 Hono RPC を利用 コードはほぼそのままECSなどに乗り換えも可能 Honoの魅力を引き出せる Bが有利。 Lambda は「ただの実行環境」

Slide 14

Slide 14 text

観点5: ルーティングの表現力

Slide 15

Slide 15 text

Hono と API Gateway で使えるルーティング条件 材料 API Gateway REST パスパラメータ /users/{cardId} {proxy+} (末尾のみ) 貪欲マッチ 正規表現パラメータ オプショナルパラメータ (別リソース定義) クエリ / ヘッダーで分岐 手段なし param / query / body の検証 Request Validator(JSON Schema) 15 Hono /users/:cardId * (どこでも) :id{[0-9]+} /posts/:id? c.req.query() / header() で分岐 Zod c.req.valid("query") API GW で出来て Hono で出来ないルーティング条件は無い 落とし穴: Hono は「登録順」優先。 /users/:id を先に書くと /users/me が食われる B 有利 — 正規表現・オプショナル・クエリ/ヘッダー分岐・Zod 検証は Hono だけ

Slide 16

Slide 16 text

観点6: CDK の書き方

Slide 17

Slide 17 text

CDK でのルーティング定義の書き方 17 A ルーティング表が CDK にある B CDK にパスもメソッドも出てこない // エンドポイントごとに Lambda 関数を1つ作る const createUserHandler = new NodejsFunction(this, "CreateUser", { this.handler = new NodejsFunction(this, "Handler", { entry: "apps/api/src/lambda.ts", entry: "src/handlers/create-user.ts", ... }); // /users を作り、POST に専用ハンドラを割り当てる const users = api.root.addResource("users"); users.addMethod("POST", new LambdaIntegration(createUserHandler), { authorizer, ... }); // /users/me を作り、GET に別のハンドラを割り当てる const me = users.addResource("me"); me.addMethod("GET", new LambdaIntegration(getUserHandler), { authorizer, ... }); // ...これがエンドポイント数だけ続く APIを追加するにはCDKのコードを修正する必要が ある runtime: lambda.Runtime.NODEJS_22_X, vpc, environment: { ... }, }); new apigateway.LambdaRestApi(this, "Api", { handler: this.handler, proxy: true, // ANY /{proxy+} → 全部この Lambda へ }); // おわり CDK が知っているのは「全リクエストを1つの Lambda に流す」だけ エンドポイント追加でインフラの 差分 は ゼロ ハンドラの中のコードはHonoのままなので読みやす い B 有利 - エンドポイント1本の追加が、A は「ハンドラ + CDK」、B は「ハンドラ」だけ

Slide 18

Slide 18 text

観点7: CloudFormation のリソース数

Slide 19

Slide 19 text

エンドポイント1本で増える CFn リソース A 1 本増やすたびに一式が増える リソース 個数 Lambda::Function 1 IAM::Role / Policy 1/1 Logs::LogGroup 1 ApiGateway::Method / Resource 1 / 1 Lambda::Permission 1 MetricFilter / Alarm 数個 1 API ≒ 十数リソース エンドポイント数だけ線形に増える B 最初の 1 式だけで増えない リソース 個数 Lambda::Function 1 IAM::Role / Policy 1/1 Logs::LogGroup 1 ApiGateway( {proxy+} ) 数個 MetricFilter / Alarm 数個 何 API 増えても ±0 ルーティングはコード側なので変わらない B 有利 - A はエンドポイントを増やすほどリソースが積み上がる。B は増えない 19

Slide 20

Slide 20 text

積み上がった先にある 500 リソース上限 1 = Method / Resource Function 20 エンドポイントで⽣成される CFn リソース ≒ 18 Function / Permission / EventInvokeConfig 3 IAM Role / Policy 2 Method / Resource 2 Alarm ×4 + MetricFilter ×4 + LogGroup 9 CloudFormation は 1スタック 500 リソースです。 → A は 1 API あたり十数リソースなので、数十 API で到達する 回避策は NestedStack への分割だが、API の分類がスタック境界に漏れ出す → CDKのコードにアプリ領域であるはずのAPIのルーティング情報が散らばってしまう B はエンドポイントが何本増えてもリソースが増えないので、この問題自体が起きない

Slide 21

Slide 21 text

まとめ

Slide 22

Slide 22 text

観点別の比較まとめ 観点 1 コールドスタート・スケー ル 2 各種機能の役割 3 権限(IAM) 4 保守性・ローカル・テスト 5 ルーティングの表現力 6 CDK の書き方 7 CFn リソース数 個人的な意見 A: 単一目的 Lambda 1回速いが頻度多い / ルート単位で制 御 Lambda 起動前に効かせられる ルート単位の最小権限 実行に別の仕組みが要る パス + メソッドのみ ルーティング表が CDK に 1 API ごとに十数個増える 22 B: Lambdalith 1回長いが頻度少ない / 全ルート共 有 Lambda 起動後(コード側) API 単位で粗くなる Hono だけで完結 正規表現 / クエリ分岐 / Zod パスが出てこない API が増えても増えない インフラ層で効かせたい要件(ルート別の認証・スロットル・最小権限)があるなら A その要件がなければ、開発体験・保守性とリソース数で優位な B で始めるのがおすすめ 有利 条件次 第 A A B B B B

Slide 23

Slide 23 text

ご清聴ありがとうございました

Slide 24

Slide 24 text

おまけ(時間があれば)

Slide 25

Slide 25 text

Lambda Web Adapter の位置づけ awslabs(AWS 公式 org)が出しているツール Express / Next.js / Spring Boot / FastAPI を無改修で 1 つの Lambda に載せる = 実態は Lambdalith を作るためのもの AWS の SA が builders.flash / ServerlessDays で積極的に推している つまり: 公式ガイドは「アンチパターン」と言いつつ、AWS 自身が Lambdalith 用ツールを出 している Yan Cui も 2018 年(分割派)→ 2025 年(「既存 Web アプリの移行に最適」)に立場を変えた 潮目は変わっている Hono アダプタ( hono/aws-lambda ) Lambda Web Adapter handler 不要(HTTP サーバをそのまま) handle(app) が必要 移植性 Node / Workers / Deno コンテナごと ECS / EKS へ コールドスタート 軽い +数十ms、初期化 10 秒制限 25

Slide 26

Slide 26 text

Lambdalith に API Gateway は必要か ルーティングをアプリに寄せた時点で、API Gateway の仕事は「プロキシ素通し」だけになる 残る API GW の価値: 認可 / スロットル / 使用量プラン / ルート別メトリクス / WebSocket それが要らないなら CloudFront + Function URL で足りる(29 秒制限からも解放、API GW 費用ゼロ) 要るなら API GW {proxy+} → Hono でも「ルーティングはアプリ側」は守れる 26