🔎

Serverless Frameworkのaws-nodejs-typescriptのテンプレートを詳しく見る

2021/10/14に公開

はじめに

Serverless Frameworkにはテンプレートを利用して作成する方法があり、私はよく aws-nodejs-typescript を利用しています。

先日、久しぶりにテンプレートを使ってみたところ、以前と中身が大幅に変わっていたので、内容の整理をしておきます。

テンプレートの構造

まずテンプレートを作成します。

npx serverless create --template aws-nodejs-typescript

作成されたテンプレートのフォルダの構造は以下のようになっています。
https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-nodejs-typescript

.
├── src
│   ├── functions               # Lambda configuration and source code folder
│   │   ├── hello
│   │   │   ├── handler.ts      # `Hello` lambda source code
│   │   │   ├── index.ts        # `Hello` lambda Serverless configuration
│   │   │   ├── mock.json       # `Hello` lambda input parameter, if any, for local invocation
│   │   │   └── schema.ts       # `Hello` lambda input event JSON-Schema
│   │   │
│   │   └── index.ts            # Import/export of all lambda configurations
│   │
│   └── libs                    # Lambda shared code
│       └── apiGateway.ts       # API Gateway specific helpers
│       └── handlerResolver.ts  # Sharable library for resolving lambda handlers
│       └── lambda.ts           # Lambda middleware
│
├── package.json
├── serverless.ts               # Serverless service file
├── tsconfig.json               # Typescript compiler configuration
├── tsconfig.paths.json         # Typescript paths
└── webpack.config.js           # Webpack configuration

初見だと、srcとlibとが分かれているかつ、ファイルが多くて何やっているかすぐにはわかりませんでした。
まずは動かしてみます

npm install 
npx serverless invoke local -f hello --path src/functions/hello/mock.json
  • 正しく実行できていることがわかります
{
    "statusCode": 200,
    "body": "{\"message\":\"Hello Frederic, welcome to the exciting Serverless world!\",\"event\":{\"headers\":{\"Content-Type\":\"application/json\"},\"body\":{\"name\":\"Frederic\"}}}"
}

利用しているライブラリについて

テンプレートのReadmeにもありますが、以下のライブラリを利用しています。

  • json-schema-to-ts
    • jsonSchemaからTypeScriptの型を生成するのに利用しています。
    • jsonの型を明示的に決めて開発する場合には利用すると便利なのかもしれません。
  • middy
    • httpリクエストのbodyはstringなので、JSONに変換するのに利用しています。
    • 要はJSON.parse()をして後続に渡しているのみです。
    • middy自体は何か処理するものではないので、ミドルウェアを追加して利用する想定なのだと思います。

middyについて

気になったので中身をちゃんと見てみました。

src/lib/lambda.ts
import middy from "@middy/core"
import middyJsonBodyParser from "@middy/http-json-body-parser"

export const middyfy = (handler) => {
  return middy(handler).use(middyJsonBodyParser())
}
  • middy(handler)しただけでは何もしない
    • middyの処理はuseにつなげるための前処理が入っているのみで何もしていません。
    • useを利用するとhandlerの処理前、処理後、エラーがあった場合に呼び出せるようになります。
    • 基本的にはuseで処理した結果はrequest.responseに値をセットして後続に渡す想定のようです。
  • middyJsonBodyParserはrequest.bodyの値をJSON.parseして上書きします。
    • middleware的なツールが元の値を書き換えるのに違和感があるのですが、いいのか。。

以前のフォルダ構造

.
├── handler.ts
├── package.json
├── serverless.ts
├── tsconfig.json
└── webpack.config.js
  • ぱっとみてhandler.tsを修正すればいいことがわかります。

新しいテンプレートは何が良くなったのか

現在のテンプレートのソースを見ていきます。

src/functions/hello/handler.ts
import 'source-map-support/register';

import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/apiGateway';
import { formatJSONResponse } from '@libs/apiGateway';
import { middyfy } from '@libs/lambda';

import schema from './schema';

const hello: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
  return formatJSONResponse({
    message: `Hello ${event.body.name}, welcome to the exciting Serverless world!`,
    event,
  });
}

export const main = middyfy(hello);

functionsでフォルダが作成されていることから、作成するlambda単位でfunctionsを作成する想定なのがわかります。
functionsは@libをいくつか利用しています。これは src/libs のフォルダのエイリアスなので、libsにドメインに依存しない共通処理が入っているようです。
サンプルのソースの中身としてはシンプルで、Hello xxxxを返しているだけのように見えます。
schemaはJSON Schemaです。event.bodyの型を定義しています。
上記のソースはよくよく見てみると、外部のライブラリに依存しない形になっています。
つまり、Serverless frameworkをAWSから別のインフラに移動した場合でも、functionのフォルダの中を修正せずに、libsのみの変更でよくなります。

テンプレートはどっちがいいのだろうか

個人的には、シンプルな昔のテンプレートのほうが好きです。理由としては以下の2点があげられます。

  • 初見で何をやっているのかわからない
  • 特定のユースケースに依存している

テンプレートはシンプルであるべきです。テンプレートを利用しようと思った開発者が簡単に何をすればいいのかを把握できる必要があります。
今のテンプレートはsrcとlibにフォルダが分かれていて、何をどう変更していけばいいのかすぐにはわかりませんでした。
以前のテンプレートでは、handler.tsしかソースがなかったので、そこにhandlerを追加すればいいのが簡単にわかってよかったです。
ただ、フォルダ構成のベストプラクティスを一緒に示したい場合にどうするといいのかは悩ましいとも思います。handlerになんでも書いていいわけではないし。

また、テンプレートではAPIGatewayを利用しているかつ、スキーマをJSON Schemaで定義している場合に限定しています。このパターンから外れる場合は、不要なものを削除したりする余計な工数がかかります。
AppSyncからのリクエストを受け付けるサンプルを作成しようとしたときに、APIGatewayもJSON Schemaもいらないので不要なものを判断して整理するのに時間がかかり面倒に感じました。

余談

  • テンプレートを出力したときに出た疑問をまとめることで、疑問点が解消されてすっきりました。
  • 初見で難しいものができると拒否したくなってしまうので、丁寧に紐解いていく必要があるとあたらめて感じました。
  • テンプレートは初めて開発する人が使うものなのか、いつも同じツールを使っている人が使うものなのかで、だいぶ構成が変わる可能性があるなと思いました。
    • Serverless Frameworkで出力するべきテンプレートはどっちなんでしょうかね。わからんけど。
  • テンプレートの変更があったPRは以下のリンクです。参考までに。
    https://github.com/serverless/serverless/pull/8646

Discussion