オリジナルKdB(科目検索)を作ってみよう -番外2- AWS Lambda編

### これまでの記事
- [オリジナルKdB(科目検索)を作ってみよう -1- Node.jsセットアップ編](https://qiita.com/tkkrr/items/2f0d93b6d0c758a88037)
- [オリジナルKdB(科目検索)を作ってみよう -2- パース編](https://qiita.com/tkkrr/items/d8a87b4f598b1b107266)
- [オリジナルKdB(科目検索)を作ってみよう -3- データで遊ぶ編](https://qiita.com/tkkrr/items/8c0f66312212420114a0)
- [オリジナルKdB(科目検索)を作ってみよう -4- Express.jsでWEBアプリことはじめ編](https://qiita.com/tkkrr/items/5cafa77c104b11d76a16)
- [オリジナルKdB(科目検索)を作ってみよう -5- Express.jsでWEBアプリ整形編](https://qiita.com/tkkrr/items/9ea97ee9b2823e3a381e)
- [オリジナルKdB(科目検索)を作ってみよう -6- 本番環境Herokuにデプロイ編](https://qiita.com/tkkrr/items/db58c25ac65da30531e9)
- [オリジナルKdB(科目検索)を作ってみよう -番外1- FaaS下準備編](https://qiita.com/tkkrr/items/077dd30091b24e64b86b)

<img width="1241" alt="スクリーンショット 2019-02-22 16.11.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/3d0c6b57-d3a9-1be1-4166-f9068a2f9771.png">

# この記事を読む前に
ある特定の人にしかわからない単語が出現する可能性が高いです。あらかじめご了承ください。

### この記事での開発環境
- MacOS 10.14.3
- Visual Studio Code 1.31.1
- Node.js LTS 10.15.1 (AWS Lambdaでは8.10)

# はじめに
「オリジナルKdB(科目検索)を作ってみよう」の番外編です。これまではExpress.jsは使わず、**サーバレス**なシステムを作っていきましょう。シリーズでやっていたようにExpress.jsでエンヤコラはやらずに、**関数を作る**だけで簡単にサービスを作ることができます。

#### 前提条件
- AWSのアカウントを持っていること
- node.jsの開発環境があること

# AWS Lambda用の関数を作成
まずはじめにAWS LambdaではNode.jsの他にPython, Ruby, Java, Go, C#(.NET)を使うことができますので、それぞれ得意な言語で書くことをオススメします。

今回は特に複雑な処理は行わないのでAWS-SDKを使わずに作れます。
それではindex.jsを作りましょう。

```javascript:index.js
// exports.handlerに格納された関数がlambdaで実行できる
// AWS Lambdaから引数としてevent, context, callbackの3つが与えられる
// それぞれの説明は後述
exports.handler = (event, context, callback) => {

    // 前回作ったdata.jsを読み込む
    const json = require("./data")

    // requestのクエリ文字列から習得(ここでは ?search=〇〇 )
    const str = event.queryStringParameters.search

    // 正規表現を作成して検索
    const reg = new RegExp(str)
    const filterCource = json.subjects.filter( (element) => {
        return element.name.match(reg)
    })

    // データ量削減のために最初の5件のみに限定
    const resJson = {
        subjects: filterCource.splice(0, 5)
    }

    // responseを作成
    const res = {
        statusCode: 200,
        headers: {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin" : "*"
        },
        body : JSON.stringify(resJson),
        isBase64Encoded: false     
    }

    // responseをクライアントへ
    callback(null, res)
};
```

※AWS Lambdaではデータ転送量が多くなるほど、実行時間が長くなるほどお金がかかります。そのためresponseを5件に絞り、`JSON.stringfy()`のコストとデータ転送量を削減しています。

## 引数についての説明

#### `event`
基本的にrequestだと考えてもらって大丈夫です。ですが、ブラウザからのHTTPリクエストだけでなく、他のAWSサービスと連携してlambda関数が使われることが多いのでrequestという引数名が使われていません。注意してください。

引数eventに入ってくる情報は呼び出しもとによって様々であり、また細かく設定することができます。今回は別のAWSのサービスである**API Gateway**と一緒に使うので、lambda関数のeventに入る情報はある程度の規約に従います。詳しくは [AWS Lambda Proxy Integrationを試してみた](https://qiita.com/_mogaming/items/2bd83204e212e35b2c6c) で説明されています。

今回はGETリクエストなので [**こちら**](https://qiita.com/_mogaming/items/2bd83204e212e35b2c6c#lambda_proxyget) に従います。特に今回使うのは`event.queryStringParameters`のみなので難しくありません。

#### `context`
これは [公式のドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-context.html) が詳しいです。

AWS Lambda で関数を実行した環境についての情報や、ログの書き出し場所、認証情報、クライアントのプラットフォーム・OS・地域情報などなど。多くの基本情報は`context`に格納されます。

lambda関数を実行してアクセスログやエラーログを取りたいときに便利な情報がたくさん入っているところです。今回は特にログは取らないので使っていません。

#### `callback`
こちらも[公式のドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-handler.html#nodejs-prog-model-handler-callback)に詳しく書いてあります。

コールバックの詳細は`callback(Error error, Object result);`となっています。
> ・error – オプションのパラメーターであり、Lambda 関数の失敗した実行結果を提供するために使用できます。Lambda 関数が成功すると、最初のパラメーターとして NULL を渡すことができます。
>・result – オプションのパラメーターであり、関数の正常な実行結果を提供するために使用できます。提供される result は、JSON.stringify と互換性がある必要があります。エラーになる場合、このパラメーターは無視されます。

とあるように、lambda関数が成功した時は`callback(null, res)`と書き、JSONオブジェクトを`callback`に渡せばいいということになります。逆にlambda関数のどこかでエラーをハンドリングした場合は`callback(error, null)`と書けばいいということになります。

つまり、`callback`の第2引数に渡されたJSONオブジェクトがクライアントへのレスポンスになります。今回はエラーハンドリングをしていないので`callback(error, null)`は出てきません。


## lambda関数のローカル実行テスト
簡単のために[ Lambda用のプログラムをローカルでテスト実行する方法（簡単編）](https://qiita.com/zaburo/items/d78a0a4462007e57d5d8)の作法に従います。次のように`local.test.js`を作ります。

```javascript:local.test.js
const { performance } = require('perf_hooks'); // 実行時間の計測用
const lambda = require('./index.js');

const event = {
    "queryStringParameters": {
        "search": "生物",   // 検索したいワードに書き換える
    }
};

const context = "";

const callback = (error, result) => {
    if(error){
        console.error(error.message);
    }else{
        console.log(result)
    }
}

const check = performance.now() // 実行時間を計測開始
lambda.handler(event, context, callback)
console.log(performance.now() - check) //実行時間の表示
```

※実際にはcontextやeventにもっと多くの情報が格納されますが、今回のlambda関数内で使っていないので省略しています。他のlambda関数を書く時は先述した[**こちら**](https://qiita.com/_mogaming/items/2bd83204e212e35b2c6c)の記事を参考に修正する必要があります。

それではテストしてちゃんとコールバックが返ってくるか確認します。

```shell
$ node local.test.js
```

ちゃんと検索結果が表示されたらOKです。
Zipファイルに圧縮してAWS Lambdaにアップロードするための準備をしましょう。

```shell
$ zip -r test.zip index.js data.js node_modules
```

#AWS Lambdaの準備
AWSのアカウントを作ったなら、AWS Lambdaのページにいきます。
こちらからどうぞ → [AWS Lambda](https://aws.amazon.com/jp/lambda/)

<img width="1440" alt="スクリーンショット 2019-03-03 16.14.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/8d7d8ac6-a711-0323-dbcb-6e34b76d1670.png">
<img width="1440" alt="スクリーンショット 2019-03-03 16.19.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/90e23137-536a-1949-5f09-be1c5d6b6834.png">
<img width="1440" alt="スクリーンショット 2019-03-03 16.22.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/fb8a51b2-c999-d4da-3c95-220b6e573f65.png">
<img width="1440" alt="スクリーンショット 2019-03-03 16.22.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/1a453c11-3495-4016-14be-1f0b61ac7a51.png">
<img width="1440" alt="スクリーンショット 2019-03-03 16.35.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/874b7fb9-1afe-d476-035a-aa4deff69014.png">
<img width="1440" alt="スクリーンショット 2019-03-03 16.35.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/18130813-8dd8-7abd-2afd-63741fd5936a.png">
<img width="1440" alt="スクリーンショット 2019-03-03 16.42.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/ff908eea-411b-a4d9-3242-d2d1066d6182.png">
<img width="1440" alt="スクリーンショット 2019-03-03 16.44.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/e671f750-b709-1ca9-983b-500f3e3268b7.png">

これでサーバレスに関数を実行できるはずです！
ですが今回の処理ではおそらく Internal Server Error が返ると思うので簡易的なトラブルシューティングをします。

# トラブルシューティング
#### 本番環境でテストしたか？
右上の「テストイベントの選択」をクリックしてテストイベントを作成する。次のように記述する。

```json
{
    "queryStringParameters": {
        "search": "生物"
    }
}
```

<img width="929" alt="スクリーンショット 2019-03-03 16.49.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/d75f4c3e-d68f-9d1a-d860-e77202af8721.png">

テストイベントを「作成」したら、実際に「テスト」を押して実行してみる。
ここでエラーが発生したらログをよく確認する。

#### 使用可能メモリ量とタイムアウト設定を確認したか？
<img width="1440" alt="スクリーンショット 2019-03-03 17.04.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/652c5ec8-3c9a-5b58-4ac5-03919534417d.png">

ログを確認してみると普通にタイムアウトしていることがわかります。なので、設定を変更して使用可能メモリ量とタイムアウト設定を変更しましょう。

<img width="1440" alt="スクリーンショット 2019-03-03 16.50.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/0704501f-e9df-e45c-7c23-750e59b94a42.png">
<img width="655" alt="スクリーンショット 2019-03-03 16.50.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/af9da20e-d895-1da9-d359-e456a480e6f3.png">

# APIが完成した！
<img width="1440" alt="スクリーンショット 2019-03-03 17.12.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/245014/6e1f447a-844b-f451-5bc9-69fdd8a86594.png">

APIのURLにクエリ文字列を指定して実行すると実際にレスポンスが返ることがわかります。
サーバがどうだとか、IPアドレスがどうだとか、ルーティングがどうだとか考えることなしに作れるのはものすごい楽です！

最初はどうしていいかわからないと思いますが、慣れると一つの関数を数分でセットアップできるようになりますし、[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-userapp.html)に書いてあるように**AWS-CLI**を使えばブラウザで操作をしなくてもコマンドラインだけでLambda関数をアップロード・API化・実行することができるので、極めれば一瞬です。

なお、今回はAPIを「オープン」で作成しましたが、やっぱりAPI-Keyを発行するなり、ユーザ認証をするなど、何かしらのセキュリティを設けたほうが確実で安全です。ここでの説明は割愛させていただきますが最低限のマナーなので気をつけたほうがいいです。

参考記事↓

- [Amazon API GatewayでAPIキー認証を設定する](https://dev.classmethod.jp/cloud/apigateway-apikey-auth/)
- [AWS Identity and Access Management (IAM)](https://aws.amazon.com/jp/iam/)

それではAWSLambda編をお送りしました。
質問等はコメントでお願いします！

次回はGCP Cloud Functionsでやっていきます！
ありがとうございました！

次の記事 → [オリジナルKdB(科目検索)を作ってみよう -番外3- GCP Cloud Functions編](https://qiita.com/tkkrr/items/cababc9130a0b6eccec4)

## 今回のあとがき
AWSやらGCPやらAzureやら...どのサービスを使うのがベストなのか...結局は好みなんじゃないかなぁと思います。個人的な考えでは、AWSはスケールアウトに優れていて各サービスの粒度が細かいので細かいところに手が届く玄人向け、GCPは他のクラウドサービスよりサービスの粒度がちょっと大きいけれどもGoogleのバックグラウンドを十二分に使えるので簡単で安定感があるため万人向けという印象です。

僕にはAWSが性に合っていたというだけですね。GCPが好きな方はもちろんGCPが性に合っているんだと思いますし、Azureが好きな人はAzureが性に合っているんだと思います。結局は好みです。

ところでクラウドサービスは本当に便利ですね。こんなに便利なサービスがあると「インフラ - バックエンド - フロントエンド」という構造は実際もうすでに古いなぁって思います。
誰でも利用できるクラウドサービスの登場でサーバ実物を管理する必要性がなくなる。これが言わんとすることは、『「バックエンド」と「フロントエンド」が求める機能を満たすためのクラウドサービスを選択する**クラウドのマエストロ**のような存在が今後のキーになる』ということかなと個人的には思っています。

それにしても、オンプレミスに頼り続ける日本は世界から遅れすぎているのでどうにかしたいものですね(巨大な主語)