Serverless Operations, inc

>_cd /blog/id_s0aw3om_6

title

SSTを使ったサーバレスアプリケーションのローカル開発プラクティス

summary

サーバレスアプリケーションを開発する上で課題の一つとなるのがAWS Lambdaのローカル開発です。サーバレスアプリケーションはその特性上様々なAWSリソースと連携した分散アプリケーションになりがちです。しかし、ローカルでそれらAWSリソースを再現することが困難なため、どうしてもデプロイしながらの動作確認となりがちです。特にCloudFormationを通したデプロイは時間がかかるため、そのデベロッパーエクスペリエンスは快適なものとは言えません。そんな中、最近SST(Serverless Stack)というサーバレスアプリケーション用のフレームワークを見つけまして、それに搭載されているLive Lambda Modeというローカル開発の手法が画期的だったので本記事で紹介したいと思います。

SSTとは?

SSTとはServerless Stackの略で、SSTが正式なプロダクト名のようです。AWS上でサーバーレスアプリケーションを簡単に開発、テスト、デプロイするためのオープンソースのフレームワークです。そして、AWS CDK(Cloud Development Kit)をラップする形で構築されており、特にサーバーレスなワークロードに焦点を当てています。Serverless FramworkやSAM、それとCDKの良いとこ取りを目指したフレームワークだと考えてもらえれば良いと思います。

訂正:V2まではCDKに依存する形で作られていましたが、V3からは依存しない形になったようです。
https://sst.dev/docs/migrate-from-v2

Live Lambda Mode

Live Lambda Modeとはローカルで開発したLambdaファンクションを即座にAWS経由で動作確認が出来る機能です。API Gateway + AWS Lambdaを使ってローカルで開発していたとすると、エディタ上で保存したコードが即座にAWS上のAPI Gateway経由で動作確認が出来ます。以前は実際にAWS環境にLambdaファンクションをデプロイするか、ローカルでAPI Gatewayのエミュレーターなどを動作させて確認するしかありませんでした。これらには以下の様なデメリットが有りました

  • AWS環境へのデプロイには時間がかかるため、コードを更新するたびにデプロイを行うと毎回待ちが発生し、スムーズな開発が行えない
  • ローカルのエミュレーターはIAM周りやAWS独自の環境などが再現できない。いざテストが完了してAWSにデプロイしても動かないということがよくあった

Live Lambda Modeを使うことで上記のデメリットは解決されて、快適なLambdaファンクションの開発経験を得ることが出来ます。具体的な内容については以降で説明します。

SSTのインストールからLive Lambda Modeのセットアップまで

まずはSSTをインストールしていきます。専用のプロジェクト用のディレクトリを作成してからnpx経由でsstをインストールします。npx sst@latest initをすると、対話型のダイアログが表示されますので、それに沿ってセットアップします。

% mkdir my-ts-app && cd my-ts-app
% npm init -y 
% npx sst@latest init

   ███████╗███████╗████████╗
   ██╔════╝██╔════╝╚══██╔══╝
   ███████╗███████╗   ██║   
   ╚════██║╚════██║   ██║   
   ███████║███████║   ██║   
   ╚══════╝╚══════╝   ╚═╝   

>  JS project detected. This will...
   - use the JS template
   - create an sst.config.ts

✓  Template: js

✓  Using: aws

✓  Done 🎉

すると以下のようなシンプルなディレクトリ構成が出来上がります。

├── package.json
├── sst.config.ts
└── tsconfig.json

sst.config.ts を開くと以下のようなコードが記述されています。async run() {} の内部に使用するAWSリソースの設定を記述していくことになります。

/// <reference path="./.sst/platform/config.d.ts" />

export default $config({
  app(input) {
    return {
      name: "my-ts2-app",
      removal: input?.stage === "production" ? "retain" : "remove",
      home: "aws",
    };
  },
  async run() {},
});

まずは、 Amazon API Gateway + AWS LambdaでHello Worldを返すだけのAPIを作っていきましょう。sst.config.ts のrun内にAmazon API Gatewayのコードを以下のように記述します。HTTPの/にGETリクエストが有れば、index.tsのhelloメソッドにルーティングされます。

async run() {
    const api = new sst.aws.ApiGatewayV2("MyApi");
    api.route("GET /", {
      handler: "index.hello",
    });
  },

ここで、Live Lambda Modeを立ち上げてみましょう。プロジェクトのルートディレクトリで以下のコマンドを実行します。

% npx sst dev 

以下のような専用のコンソールが立ち上がってAmazon API Gatewayのエンドポイントが発行されたことがわかります。

そして、index.tsを作成して、Hello Worldをレスポンスするメソッドを作成します。

export async function hello() {
  return {
    statusCode: 200,
    body: "Hello World",
  };
}

エディタで保存後発行されたエンドポイントにアクセスするとすぐにHello Worldが返ってきました。

% curl https://04kyvqsb3d.execute-api.us-east-1.amazonaws.com/
Hello World

レスポンスの内容をHello WordからHelloに変更すると、デプロイで待たされることなくすぐにAmazon API Gatewayのレスポンスに反映されています。これがLive Lambda Modeの凄さです。

% curl https://04kyvqsb3d.execute-api.us-east-1.amazonaws.com/
Hello                                       

もし、構文エラーなどが発生していた際もLive Lambda Mode起動時に立ち上がったコンソールからリアルタイムにログを確認することが可能です。

Function Logs

Waiting for invocations...

|  Build       MyApiRouteDnvusbHandler
|  Invoke      MyApiRouteDnvusbHandler
|  Done        took +461ms
|  Invoke      MyApiRouteDnvusbHandler
|  Done        took +451ms
|  Build       MyApiRouteDnvusbHandler
|  Invoke      MyApiRouteDnvusbHandler
|  Done        took +458ms
|  Build Error MyApiRouteDnvusbHandler
|  ↳ Unterminated string literal index.ts:4:17

Amazon S3を使ったサーバレスアプリケーションをSSTで開発する

Hello Worldだけでは少し物足りないので、S3バケットにAmazon API Gateway経由でファイルのアップロードを行うアプリケーションを作ってみましょう。具体的にはAmazon API Gateway経由でAmazon S3の署名付きURLを発行してそこからファイルのアップロードを行うといったものです。

まずはS3のクライアントをnpmでインストールします。

% npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

そして、sst.config.ts にてAmazon API GatewayとAmazon S3の指定を行います。

async run() {
    const bucket = new sst.aws.Bucket("MyBucket", {
      access: "public"
    });
    const api = new sst.aws.ApiGatewayV2("MyApi");
    api.route("GET /", {
      link: [bucket],
      handler: "index.upload",
    });
  },

index.ts内では署名付きURLを発行してそれをAPIのレスポンスとして返す処理を作ります。S3バケットのキーは後で動作確認して分かりやすいようにuploaded-fileと命名しておきます。これのファイルが作られていれば処理成功です。

import { Resource } from "sst";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import {
  S3Client,
  PutObjectCommand,
} from "@aws-sdk/client-s3";

const s3 = new S3Client({});

export async function upload() {
  const command = new PutObjectCommand({
    Key: 'uploaded-file',
    Bucket: Resource.MyBucket.name,
  });

  return {
    statusCode: 200,
    body: await getSignedUrl(s3, command),
  };
}

動作確認でpackage.jsonを以下のコマンドでアップロードしてみましょう。

% curl --upload-file package.json "$(curl https://xk28df6no8.execute-api.us-east-1.amazonaws.com)"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1462  100  1462    0     0    563      0  0:00:02  0:00:02 --:--:--   565

以下のようにS3バケット上にuploaded-fileというオブジェクトが生成されていました。処理成功です。そしてここまでAWS Lambdaのデプロイは行っていません。すべてローカルのコードをAmazon API Gateway経由で動かしています。

まとめ

今回はSSTを使ってLive Lambda Modeの紹介を行いました。直接デプロイする手間がなくなったことでかなり快適にLambdaファンクションの開発を行うことが出きました。まだまだ、日本ではSSTはメジャーとは言えませんが、是非機会があれば試してみてください。

Written by
CEO

堀家 隆宏

Takahiro Horike

  • Facebook->
  • X->
  • GitHub->

Share

Facebook->X->
Back
to list
<-