Serverless Framework と TypeScript で Hello と Version の Web API を 実装

前回サーバーレスなアプリケーション の ひな形を作りました。
今回は、ひな形で作られた AWS Lambda の ソースを Hello World にし、また新しくアプリケーションのバージョンを返す Version Web API を 追加します。

シリーズの記事

環境

開発者の環境は以下となります。

  • Windows 10 64bit
  • Visual Studio Code
  • Node.js 9
  • TypeScript 2.7
  • Serverless Framework 1.26.0

主な実行環境について、また全部稼働まではいきませんが以下を想定しています。
※ Amazon API Gateway などは、Serverless Framework が 自動設定するので省略、明示的に使うものとしてリストになります。

  • Amazon S3
  • AWS Lambda

ベースとなるソースコード

前回の続きとなりますので、こちら https://github.com/riotz-works/samples-hello-serverless/tree/0.0.1 を もとに追加開発します。

本記事と一緒に作成する場合は 前回のソース もしくは タグを指定してチェックアウトしたソース を お使いください。

1
2
3
c:\Temp\nlog>git clone --depth 1 -b 0.0.1 https://github.com/riotz-works/samples-hello-serverless.git
Cloning into 'samples-hello-serverless'...
(省略)

Hello Web API の 改修

Serverless Framework の ボイラープレート で 生成した Lambda の プログラム は handler.js で 以下のようになっています。
これを Hello World の Web API へ 改修するにあたり、このままで responsebody を 変えるだけでよさそうです。今回は message'Hello Serverless !!' にし、input は 削除することにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { APIGatewayEvent, Callback, Context, Handler } from 'aws-lambda';

export const hello: Handler = (event: APIGatewayEvent, context: Context, cb: Callback) => {
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!',
input: event,
}),
};

cb(null, response);
}

そしてプロジェクトのディレクトリ直下で汎用的な名前の handler.js だと、このあと追加する Version Web API の 配置などで困ります。
まずはディレクトリ配置を見直し、Lambda の ハンドラ・ファンクションのソースが入っていることがわかるように src/handler 配下に置くことにします。
続いて Hello Web API の ハンドラに対応したファンクションのファイル名として greetings.ts とすることにします。

ファイル名と配置を変えたので Lambda の 定義をしている serverless.yml を 修正します。
hello ファンクション の ハンドラ は handler.hello と 定義されていましたが、greetings ファクション src/handler/greetings.hello ハンドラ に 変更します。

Hello Web API が できました。
この時点で serverless deploy コマンドでデプロイすることもできます。(–aws-profile [profilename] を 忘れずに)

Version Web API の 作成

Hello Web API が 作れましたがボイラープレートのままだったので、新しく Web API を 追加したいと思います。
とりあえず、アプリケーションのバージョンを返す Version Web API を 作ることにします。
仕様は「HTTP GET /versionpackage.jsonversion を 返す」にします。

まずは、Lambda の ハンドラのソースを配置する src/handlersystems.ts を 作ります。
他にシステム関連の Web API の 想定はありませんが、システム関連 Web API の ハンドラを配置する systems.tsversion の ハンドラを追加するイメージにしました。

コードは以下のようにします。(このコードを使う場合は、1行目の Copyright は 書き換えてください)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Copyright 2018 Riotz Works. */
import { APIGatewayEvent, Callback, Context, Handler } from 'aws-lambda';
import { version } from '../../package.json';

/**
* System Web API's AWS Lambda handler function.
*
* @param event – event data.
* @param context – runtime information of the Lambda function that is executing.
* @param callback – optional callback to return information to the caller, otherwise return value is null.
* @see http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
*/
export const handle: Handler = (event: APIGatewayEvent, context: Context, cb?: Callback): void => {

const response = {
'body': JSON.stringify({
'version': version
}),
'statusCode': 200
};

if (cb !== undefined) {
cb(null, response); // tslint:disable-line
} else {
context.succeed(response);
}
};

主なキー・ポイントは以下になります。 先の Hello Web API と 若干異なる部分 (context.succeed() とか) が ありますが詳細は後日の記事として、基本的には変わりありません。

  • 3行目で、バージョンのもとになる package.jsonsystems.ts の 相対パスで指定し version 要素をインポートします
  • 17行目で、インポートした version 属性の値 を レスポンス・ボディとしてセットします

Visual Studio Code の 環境によってはエラーが出ないかもしれませんが、3行目 の インポート文でエラーにあります。

何も表示されていなくても serverless package を 実行するとエラーが出力されます。

1
2
3
4
5
C:\Temp\samples-hello-serverless> serverless package
Serverless: Bundling with Webpack...
(省略)
ERROR in C:\Develop\repos\riotz\samples-hello-serverless\src\handler\systems.ts
(3,25): error TS2307: Cannot find module '../../package.json'.

これは package.json の モジュール定義が存在しないためになります。
このように型定義などを確認してくれるのが TypeScript のありがたいところです。
一方で今回のは若干無茶な実装でした。標準モジュールには存在しませんし、適切なモジュール定義をとってくることも難しいでしょう。さりとて package.json と 何か別ファイルで version の ダブルメンテはしたくないです。。。 ということで、モジュール定義を追加することにします。
ソースディレクトリ の srctypes.d.ts を 追加します。.d.ts ファイルでしたら特に命名規則はなく typing.d.ts も よく見かけます。

コードは以下のようにします。(1行目の Copyright は 書き換えてください)

1
2
3
4
5
6
7
8
9
10
/* Copyright 2018 Riotz Works. */

/**
* Module declaration for JSON file.
*/
declare module '*.json' {

/** version element of package.json using Version API. */
const version: string | undefined;
}

*.json なので、モジュール定義が見つからなかった JSON ファイル の 型定義になります。
その中に version が 文字列として存在することを定義しますが、これは 全ての JSON ファイルに当てはまることではないので、型定義は string | undefined とし ドキュメントで注釈しました。
頑張ると package.json 固有のモジュール定義もできたのですが version を取得するだけにしては複雑になりすぎたので、このあたりを落としどころとしました。

最後に serverless.yml に Version Web API の Lambda 定義を追加します。
functions: greetings: の 下に追加します。

1
2
3
4
5
6
systems:
handler: src/handler/systems.handle
events:
- http:
method: get
path: version

Version Web API が できました。

デプロイ

AWS 環境へデプロイします。前回の デプロイ と 稼働テスト 同様に、AWS の プロファイを参照して serverless deploy --aws-profile [profilename] を 実行します。

endpointshelloversion の URL が 出力されるので、それぞれブラウザでアクセスすると、プログラムした JSON が 返ってきます。

ソースコード

今回作成した部分までのソース を GitHub へ アップしました。
https://github.com/riotz-works/samples-hello-serverless/tree/0.0.2


Hello Web API は ボイラープレート の レスポンスを改修しただけだったので特に大きな変化はありませんでした。一方で Version Web API は 新しく追加となりましたし、両方のプログラムを配置するためにプロジェクトの構造も変化させました。

Hello と Version で 実装レベルが異なっている部分はありますが、これについては次の記事で確認しながら合わせこみをしていきたいと思います。できれば TypeScript の コンパイラ設定 や Lint にまでふれられたらと思います。

Author: lulzneko

Serverless Framework と TypeScript で サーバレス開発事始め

前回の記事から時間が空いてしまいましたが、ちょうど新しいプロダクトの開発を始めるところなので Serverless FrameworkTypeScript で 開発する際の事始めについてまとめます。
例によって Hello World になりますが、こんな感じで作り始めますというところを お伝えできればと思います。

シリーズの記事

環境

開発者の環境は以下となります。

  • Windows 10 64bit
  • Visual Studio Code
  • Node.js 9
  • TypeScript 2.7
  • Serverless Framework 1.26.0

主な実行環境について、今回は準備なので全部稼働まではいきませんが以下を想定しています。
※ Amazon API Gateway などは、Serverless Framework が 自動設定するので省略、明示的に使うものとしてリストになります。

  • Amazon S3
  • AWS Lambda

想定アプリケーション

ブラウザでアクセスするウェブサイトで、アクセス時に JavaScript で Web API を 呼び出し “Hello World” の 挨拶を取得します。 まずは動作させるところを目標にシンプルな形で進めます。

ウェブサイト は Amazon S3 に HTML を 配置し 静的ウェブサイトのホスティングを使います。
本格稼働するには Amazon CloudFrontAmazon Route 53 などが必要となりますが、まずは稼働を目指し S3 だけで進めます。

Web API は サーバーレスで作りたいので AWS Lambda を 使います。
インスタンス や コンテナ の 管理が不要で、純粋にアプリケーションの開発やメンテに集中できるのサーバーレスにこだわりたいです。
こちらも本格稼働にあたっては Amazon API Gateway に カスタム・ドメインを割り当てたりしますが、まずは稼働を目指し自動割り当てで進めます。

開発環境の構築

コーディングにためのエディタを用意します。こちらは手慣れたもので大丈夫です。今回は Visual Studio Code を 使いました。
こちら https://code.visualstudio.com から取得できます。

続いて Node.js を インストールします。こちら https://nodejs.org から ダウンロードしてインストールします。今回は 9.5.0 Current を インストールしました。

プロジェクト の 作成

Node.js が インストールできたら、プロジェクトのディレクトリを作成します。
今回は C:\Temp\hello-serverless を プロジェクトのディレクトリとしました。

続いて Serverless Framework を 導入します。今回は Visual Studio Code の ターミナルを使っていますが、コマンド プロンプト からも同様のコマンドになります。
今回はプロジェクトごとに Serverless Framework を 導入したいので、-g なしで npm install serverless としました。

1
2
3
4
C:\Temp\hello-serverless> npm install serverless
(省略)
+ serverless@1.26.0
added 302 packages in 44.148s

Serverless の モジュール が 導入され、node_modulespackage-lock.json が 作られています。

Serverless の AWS/TypeScript ボイラープレート から プロジェクト の ひな形を作ります。
※ 今回はプロジェクト・ローカル に Serverless Framework を 導入しているため node_modules\.bin\serverless コマンドになります。node_modules\.bin に パスを通しておくか、めんどくさい場合は npm install -g serverless-g を つけてグローバルに入れてしまうのも手です。

1
2
3
4
C:\Temp\hello-serverless> node_modules\.bin\serverless create --template aws-nodejs-typescript
(省略)
Serverless: Successfully generated boilerplate for template: "aws-nodejs-typescript"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name

Node.js/npm プロジェクト関連のファイルが追加されます。

package.jsondevDependencies に Serverless Framework が 入っていないので、もう一度 --save-dev を 付けて npm install --save-dev serverless を 実行します。
※ 最初から --save-dev しないのは package.json が 作られているとボイラープレートが展開できないためです。

他のモジュールもインストールするため、npm install を 実行します。

プロジェクト の 設定

自動生成された設定が入っているので、プロジェクトに合わせた設定を行います。

package.json は、最小限 name description author あたりを変更しておきます。
また、作成しているのはアプリケーションなのでモジュール公開の防止のために "private": true を 設定しておくとよいでしょう。private の 詳細は こちら package.json | npm Documentation を ご確認ください。

serverless.yml は、service: name: の 値を変更しておきます。

tsconfig.jsonlibesnext を 追加します。
2018年2月現在、これを追加しておかないとビルド時に参照しているモジュールで以下のようなエラーが発生するためです。

1
2
3
4
5
6
7
C:\Temp\hello-serverless> node_modules\.bin\serverless package

ERROR in C:\Temp\hello-serverless\node_modules\@types\graphql\subscription\subscribe.d.ts
(17,4): error TS2304: Cannot find name 'AsyncIterator'.

ERROR in C:\Temp\hello-serverless\node_modules\@types\graphql\subscription\subscribe.d.ts
(29,4): error TS2304: Cannot find name 'AsyncIterable'.

プロジェクト の ビルド

serverless package コマンド で ビルドします。

1
2
3
4
5
6
7
8
9
C:\Temp\hello-serverless> node_modules\.bin\serverless package
Serverless: Bundling with Webpack...
ts-loader: Using typescript@2.6.2 and C:\Temp\hello-serverless\tsconfig.json
Time: 2085ms
Asset Size Chunks Chunk Names
handler.js 2.94 kB 0 [emitted] handler
handler.js.map 3.18 kB 0 [emitted] handler
[0] ./handler.ts 350 bytes {0} [built]
Serverless: Packaging service...

TypeScript が コンパイルされ、JavaScript と ソースマップ が 作られ、.serverless ディレクトリに AWS CloudFormation の テンプレート と Lambda の デプロイ Zip が 作られます。

デプロイ と 稼働テスト

ビルドに成功したら serverless deploy コマンド で AWS 環境 へ デプロイします。
デプロイにあたっては AWS IAM ユーザー の アクセス・キー が 必要となります。あらかじめ作成アクセス・キーを取得し、%USERPROFILE%\.aws\credentials (e.g. C:\Users\username\.aws\credentials) に 記述しておきます。
IAM の アクセス権 は PowerUserAccess では足りず IAM 周りの権限追加が必要です。まだ絞り込みきれておらず結局 AdministratorAccess を 使ってしまっています。頑張らないと。。。

1
2
3
4
[hello]
region = us-west-2
aws_access_key_id = ZDEJREC4D1GM3DXXXX
aws_secret_access_key = ce4FH4Dcyh5DGh2dDGG4hDk6DYfhDAjrFhXXXX

デプロイを実行します。
serverless deploy --aws-profile hello--aws-profilecredentials[] で 指定したプロファイル名を指定します。

1
2
3
4
5
6
C:\Temp\hello-serverless> node_modules\.bin\serverless deploy --aws-profile hello
(省略)
endpoints:
GET - https://ca3kg0hrXX.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
hello: hello-serverless-dev-hello

無事にデプロイできると 自動生成された API Gateway の エンドポイントが出力されます。

ブラウザでアクセスすると、handler.ts の 実装の通り「Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!」の メッセージ と HTTP リクエストの内容が表示されます。

お掃除

serverless remove で AWS 環境をクリーンアップすることができます。

1
2
3
4
5
6
7
C:\Temp\hello-serverless> node_modules\.bin\serverless remove --aws-profile hello
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
..........
Serverless: Stack removal finished...

ソースコード

今回作成した部分までのソース を GitHub へ アップしました。
https://github.com/riotz-works/samples-hello-serverless/tree/0.0.1


初期設定に多少の作業がありましたが、Serverless Framework を 使うことで簡単にデプロイとクリーンアップができました。

アプリケーションとしては Lambda で メッセージを返すレベルではありますが、インフラ面を考えると、これを全て AWS マネジメントコンソール ではかなりの手間がかかります。それを Serverless Framework が 補ってくれるので、サーバレス開発のインフラ運用が削減できるメリットに加えて、インフラ構築も格段と楽になるのではないでしょうか。

また Try & Error を 繰り返すうちに AWS 環境 が ついつい散かってしまい、よくわからないリソースが放置されるケースが出てきたりします。開発環境とはいえ Serverless Framework で 環境を作るようにすると片付けも簡単ですね。

引き続き、このプロジェクトに設定やコードを加えて もう少し発展させていきたいと思います。

Author: lulzneko

CSV ファイル の バッチ連携 も AWS Lambda で サーバーレス

この記事は Serverless Advent Calendar 2017 の 7日目になります。

Serverlessconf Tokyo 2017 で 発表させていただく機会をいただき、 Java チームが選択した TypeScript による AWS Lambda 開発 というタイトルで お話をさせていただきました。

今回は そのシステムの裏手側、CSV ファイル を 連携するバッチ を サーバーレスで実現したアーキテクチャについて ご紹介します。

システム全体の概略図

ざっくりと下図のようなシステムで AWS 上に作られています。
スマートフォン などから IoT 機器を操作するようなクラウド・サービスがあり、そのバックグラウンド・プラットフォームになります。認証や機器のデータ管理を行うシステムです。
このシステムは、図の Amazon API GatewayAWS Lambda が ペアになっているアイコン毎が1つ1つのマイクロサービスとして作られています。つまり 10を超えるマイクロ・サービスの集合体になります!

今回の話は、この図の右上部分 “Traditional DC”、企業の伝統的なデータセンターからデータを受ける部分です。
この部分は伝統とシキタリにのっとり CSV ファイル を 受け取り、処理する仕組みが必要となります。

※ CSV の C は Comma と 信じてましたが、Character や Column 説 (e.g. Wikipedia) が あり 広義には Comma に 限らず特定の文字でデータを区切るファイルらしい。どうでもいいけど。。。

なぜ AWS Lambda ?

このようなシステム を AWS で 実装するには、AWS BatchAWS Glue などを使うかと思います。しかしながら、今回は Lambda で 実装することにしました。

背景としては、以下のような点があります。

  • 少人数の DevOps チームであり、AWS の マネージド度合いが高いほど嬉しかった
  • データ量や処理量から想定してコスパが優れていた
  • 1回に連携するデータ量 が Lambda の 処理時間におさまる可能性が持てた

特に2番目のコストについては、Glue に対して圧倒的によかったというのが大きかったです。今回の処理は ETL に近いとはいえ、DWH 連携ではなく、DB の マスター・データ登録だったので Glue を 活かしきれないという点がありました。

なお Lambda の 実行ランタイムは Java に なります。
(Serverlessconf Tokyo 2017 の 発表資料 の 通り、Java チーム なので)

CSV ファイル 連携 の アーキテクチャ概要図


“Traditional DC” からは、Amazon S3 に CSV を おいてもらうことにします。FTP/SFTP とのリクエストがありましたが、サーバーレス厨 とは言わずとも ここは S3 へのアップロードへお願いしたいところ、何としてでも承諾いただきます。(いただきました)

S3 からは Amazon SNS へ イベント通知をし、Lambda を 起動します。その Lambda で CSV を パースして DynamoDB へ データを登録します。

複雑なデータの加工 や 外部の API/データ を 参照するような場合は、DynamoDB Streams から Lambda を 呼び出して後続の処理として実装します。このようにすることで、データの登録に特化させて処理数を稼ぐことができ、また DynamoDB Streams の 後続処理は、登録されたデータ1件ごとの処理になるので時間的な余裕が生まれます。

ここでのポイントは S3 ⇒ SNS からの Lambda は DynamoDB への登録だけに特化し、他の処理をしないことです。このようにすることで、なるべく CSV ファイルの対応に処理を振り分けるようにし、時間内で処理できる量を増やすようにしています。

処理能力 と アーキテクチャの改良

このような形にすることで、CSV を Lambda で 処理できるようになります。
このアーキテクチャの処理能力は、CSV ファイル 1行のデータ量や、カラム数によって変わってくる部分があるかと思いますが、今回のケースでは 1ファイル で 約 800行のレコードが処理できました!
て、まったく処理できてません。。。

これでは役に立たないので対応が必要となります。
Lambda は 設定するメモリの使用量によって処理速度が変わるという話もあり、最大に設定してみましたが設定による差は特にありませんでした。ファイルを読み込みながらの順次処理なので、ここでの処理性能はあまり変わらないのでしょう。また DynamoDB へ アクセスするオーバーヘッドも処理性能では大きく変わらないはずです。

では、どうするのか。


S3 に 置かれたファイルを DynamoDB へ 登録する前に、ファイル分割する処理をはさみます。
1回の Lambda で DynamoDB へ 登録できるのは 余裕を見て 500件までとして、CSV ファイルを 500行ごとのファイルに分割して S3 へ 再アップロードします。その 500行ごとのファイルを Lambda で DynamoDB へ 登録する形にしました。

これにより、30,000行まで処理できるようになり現実的なラインまで持っていけるようになりました。S3 からの Lambda に 変わりはないので、サーバーレスの形態も維持できます。

まとめ

サーバーレスでシステムを作ろうとしても、往々にして発生してしまう既存システムとのバッチ連携。そのために FTP/SFTP やら、CSV パースするための処理やら と どうしても サーバ・インスタンスが欲しくなってしまい、せっかくの サーバーレス が サーバー有り(サーバーレスの反対語って何だろう)になってしまいます。

Glue に 見合う処理であればよいのですが、今回のように小規模なデータ登録となるとオーバースペック気味にもなります。そんな時には S3 からの Lambda で 処理することで、サーバーレスでバッチ連携をするのも手ではないでしょうか。

ポイントとしては以下になります。

  • ファイルは S3 に 置いてもらう
  • S3 ⇒ SNS から Lambda を 起動し DynamoDB へ データ登録に特化する
  • 1回の Lambda で処理できるデータ量は少ない、必要に応じてファイル分割の Lambda を はさむ

Serverless Advent Calendar 2017 、楽しく、そして勉強になる記事がたくさんです。

昨日 6日目 は @RyoheiMorimotoさん の AWS LambdaをTest Runnerとして使ってみる でした. ユーザ管理のシステムをサーバーレスで 34歳 タローさん が ハナコさん に なれるかをテストしています。サーバーレス・テスト、面白いですね!

明日 8日目 は @narikeiさん です。何の記事か楽しみです!


では、ハッピー・サーバーレス・ライフ! ハッピー・ホリデー!!

Author: lulzneko

Hello World !!

こんにちは、世界!
こんにちは、みなさん!!

わたしたち は Riotz Works です。

“a cheerful engineering team” を 標榜する、わたしたちは
IT を 中心に 技術的なこと や さまざまな挑戦 が 大好きで 陽気で愉快な エンジニアの集団です。

これまで学んできたこと、今挑戦していること、先々やってみたいこと など、を 面白おかしく、まるで お祭り騒ぎ のように アウトプットしていきたい、そんな思いから、この『 Articles | Riotz Works 』という場を作りました。

またエンジニアにとっての最初の挨拶は、何といっても “Hello World !” ですね。
ということで最初のポストのタイトルも “Hello World !!” に しました。

この記念すべき Hello World は プログラム言語でいうと、どんな感じだろうかと「Hello worldプログラムの一覧 - Wikipedia」を眺めていたら、これかな!というのがありました。

1
import __hello__

Python の コードで、”プログラマーに必要な機能をすべて標準ライブラリーとして提供する「バッテリー同梱 (batteries included)」の思想を具現化するジョーク - Wikipedia」なのだとか。

さすがに「すべて」とまではいかないですが、エンジニアにとって必要なものが たくさんアウトプットされている場にしていきたいと考えています。

わたしたち の アウトプット が わずかでもお役に立てれれば、勉強させていただいているコミュニティへ少しでも還元できれば、そして 新たな出会いと挑戦につながればと考えております。

どうぞ よろしくお願いいたします!!

Author: lulzneko