図解で、ハンズオン（AWS API gateway, Lambda, dynamoDB）

#ハンズオン（AWS API gateway, Lambda, dynamoDB）
「Lambda未経験者限定」の超入門編です。なるべく図表を多用して書きます。分からなかったところは教えてください、補足を書き足します。60分でひと通り試すことができます。

##ゴール設定
自分の言葉で「サーバーレスって何？」を話せるようにするために、サーバーレスで典型的なAWS Lambdaの構成を30分で試せる最もシンプルなデータの書き込みと読み込みを仮想サーバー無しでやってみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/11675b46-f498-c36b-5e33-09e9f4d46667.png)

##必要なもの
- パソコン
- AWSアカウント
- サーバレスって何という疑問

##目次
- （１）dynamoDB（**まずデータを用意します**）
    - （1-1）テーブルを作成する。
    - （1-2）データを書く。
- （２）Lambda（**とりあえずデータを読んでみます**）
    - （2-1）こんちわ
    - （2-2）データを読む。
- （３）API gateway（**APIを体験します**）
    - （3-1）WebブラウザでHTTPSアクセスする。
    - （3-2）Lambda再び（**ついでなのでデータを書いてみます**）

##アーキテクチャ
AWS管理コンソールにログインして、下図のシステム構成を作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/0db4e6b1-fcdc-be3d-5dff-d2c05dd22f88.png)

ではここからハンズオンスタート。

##（１）dynamoDB（**まずデータを用意します**）
dynabmoDB［テーブル］を作って、レコードに相当する［項目(Item)］を定義します。ここではオンプレに慣れた人向けに図を書いていますが、Dataの部分は、正しくは「属性（attribute）」と呼びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/31ddef4c-b25f-c0e2-b0a7-aadc2c461564.png)

###（1-1）テーブルを作成する。
AWS管理コンソールからDynamoDB［テーブルの作成］を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/410b51dc-73ed-2d46-a704-d0ffaa15f99a.png)

［テーブル名」と［パーティションキー］に好きな名前を付けて［作成］を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/97c06b58-9086-e7ff-6594-0470b9f0100a.png)

空っぽのDynamoDB［テーブル］ができました。次は自分のテーブルをクリックしてデータを書いてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/83b3da69-f535-a408-b54c-efe099757c8b.png)

###（1-2）データを書く。
用語が紛らわしいのでdynamoDBの図を見ながら進めてください。リレーショナルDBでいうデータレコードを１つ書きたいのですが、dynamoDBでは項目とかアイテムとかいいます。そしてデータカラムに相当するのが属性っていいます。しっくりこないです。さて、データレコードを書き込み、言い直しすると項目（アイテム）を追加したいので［項目タブ］→［項目の作成］を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/87597eec-35a8-73bf-05bb-e8c13b35ace1.png)

もう一度いいます、用語に気を付けて最初のデータを書きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/1b7bdb78-2f6d-0188-dc31-c8f6869aa54d.png)

項目（アイテム）を２つ追加したときの例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/9cb407ba-b84a-47b4-f3ff-8b48b01bcd94.png)


##（２）Lambda（**とりあえずデータを読んでみます**）
いよいよ、DynamoDBに書き込んだデータ［項目（アイテム）］をLambdaで読み込みます。ここでは［Lambda関数］を作ります。関連する設定が幾つかあります。実行IAMロールの設定は必須。その他はオプションなので必要に応じて設定します～　例えば：　VPC内で使うための設定。コードの外に環境変数を持つ場合。PHP用SDKやpythonなどの外部ライブラリはZipファイルにまとめてアップロードできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/36bcd082-b536-4fac-fb63-14627f906b01.png)

###（2-1）こんちわ
では、AWS管理コンソールでLambdaを見つけて［関数の作成］を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/e900f28c-d00b-965a-9df1-d1a441e1378d.png)

続いて、下図を参考に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/2c4287e3-f836-ca40-4b4b-dd44d2caa778.png)

実行IAMロールは［新しくIAMロールを作る］もしくは［既存IAMロールを使う］から適宜選んでください。IAMロールに与えるポリシーは、今回はお試しなので［Lambda］と［DynamoDB］のそれぞれフルアクセス権限とします。本番では最低限に調整します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/ba7c3a39-b14b-f678-b478-7341be05d5ad.png)

###（2-2）データを読む。
Lambda関数コードを書きます。最後に［保存］を実行。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/b39fd091-16bc-2f8b-8ad2-7a2c764de5dd.png)


```lambda_function.py
import json
import boto3

def lambda_handler(event, context):
    # テーブル定義
    table_name = 'KanoTable'
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(table_name)

    # レコード読み出し
    response = table.get_item( 
        Key = { 
            'TargetKey': 'kano',
            'TimeStamp': '20200107 23:07' 
        } 
    )
    
    print(response)

    return {
        'statusCode': 200,
        'headers': { 'Content-type': 'application/json;charset=UTF-8' },
        'body': json.dumps(response)
    }
```

データの読み込みを［テスト］してみよう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/c5616e5f-c262-0167-8c9e-8bb464fb5d9a.png)

”成功”って表示されたらHappy。詳細をクリックすると実行結果のデータが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/c3f3f4d0-cbb7-c1cf-70ff-300deae16cd8.png)

##（３）API gateway（**APIを体験します**）
今度は、WEBブラウザのURLでLambdaにアクセスして、データをブラウザに表示します。準備するのはAPI Gateway。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/e39b8998-2bac-e44f-da80-d7f79ce022c9.png)

###（3-1）WebブラウザでHTTPSアクセスする。
Lambdaを編集したDesignerという画面で［＋ トリガーを追加］を実行して、下図を参考にAPIを設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/e83187f8-b95c-d26b-2d7e-9015518ec92a.png)

WEBブラウザでLambdaを起動してみよう。下図をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/d2e56a79-58e6-67d6-ef46-6c86fb2a2952.png)

WEBブラウザにJSON形式のデータが表示されたら、とりあえずDynamoDBのデータ読み出しが成功とする。

```WEBブラウザの画面
{"Item": 
  {"Content": "\u6700\u521d\u306e\u30ec\u30b3\u30fc\u30c9\u3060\u3088", 
  "TimeStamp": "20200107 23:07",
  "TargetKey": "kano"},
  "ResponseMetadata": 
     {"RequestId": "1A5*****SUAAJG",
      "HTTPStatusCode": 200,
      "HTTPHeaders": {略}, "RetryAttempts": 0}}
```


###（3-2）Lambda再び（**ついでなのでデータを書いてみます**）
ここは復習編。再び（2-1）から順に新しいLambda関数を作成します。今度はデータを書くLambdaです。

```putRecordDynamoDB.py
import json
import boto3

def lambda_handler(event, context):

    # テーブルを定義
    table_name = 'KanoTable'
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(table_name) 
    
    # レコード書き出し
    response = table.put_item( 
        Item = { 
            'TargetKey': 'new',
            'TimeStamp': 'du',
            'Content': '追加しました'
        } 
    )
    
    return {
        'statusCode': 200,
        'body': json.dumps('Record Added!')
    }

```

##まとめ
世の中でサーバーレスと言われている典型的な「API Gateway」「Lambda」「dynamoDB」の３つを組み合わせて、データを書いて、読むというWEBシステムの基本的なプログラミングを体験できたと思います（できました？）。準備含めて60分でハンズオンする内容なのでもやもや感はあるかと思います。この後は時間をかけて、データ入力用のHTMLファイルとデータを整形して表示するHTMLファイルを作成してS3などに保存して、そのHTMLからWEBブラウザを起動すれば、簡単な掲示板アプリみたいなのを作れると思います。

以上、おしまい。

.
.
.
.
.
.
.
.




##以下、参考。

##トラブルシューティング
###CORS(Cross-Origin resource sharing) オリジン間リソース共有が問題？
WEBブラウザの開発者ツールでデバッグ。CORSポリシーでブロックされている、とある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/7f88a422-ba74-818f-92b9-3f305f81096a.png)

API Gateway のクロスオリジンリソース共有で、他のサイトからのアクセスを許可する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189936/bb60e847-4dda-0ed1-e5e7-95f50e690226.png)

##APIで得たjsonをWEBブラウザに表示したい
フロントエンド側で静的ファイルをWEBブラウザで表示する(apacheで実行)。

```lambda_get.html
<!DOCTYPE html>
<html lang="ja">
<html>
  <head>
  <meta charset="UTF-8">
  <title>Lambda json 読み出し</title>

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
    <script>
      window.onload=function(){
        $.ajax( {
          type: "get",
          dataType: "json",
          url : "https://d6****y5.execute-api.ap-northeast-1.amazonaws.com/default/getRecordDynamoDB",
          success : function( json ){
            console.log(json)
            //console.log(json.Item.Content);     //１アイテムの場合
            //console.log(json.Items[0].Content); //複数アイテムの場合
            $("#msg-result").text("success: "+json.Count+" pcs");

            strTemp = "<table border=1>";
            json.Items.forEach( function(dbitems) {
              strTemp += "<tr><td>"+ dbitems.Content +"</td>";
              strTemp += "<td>"+ dbitems.TargetKey + "</td>";
              strTemp += "<td>"+ dbitems.TimeStamp + "</td></tr>";
            });
            strTemp += "</table>";
            $("#msg").html( strTemp );

          },
          error : function (xhr,text,thrown){
            $("#msg-result").text("error : "+text);
          }
        });
      }
    </script>

  </head>
  <body>

    <h1>dynamoDBからアイテムを読み出し</h1>
    <p id="msg-result">問合せ中...</p>
    <div id="msg">問合せ中...</div>

  </body>
</html>
```


##DBに書き込む値をWEBブラウザの引数で渡す場合
WEBブラウザのURL入力をこんな（↓）引数で渡したいので

```
# ブラウザの場合
https://xxxxxx.amazonaws.com/putRecordDynamoDB?Key=Ichiro&Content=(^^)%20/~~~
# linuxの場合(&でなく\&)
curl https://xxxxxx.amazonaws.com/putRecordDynamoDB?Key=Ichiro\&Content=(^^)%20/~~~
```

lambda関数コードを改造する。event引数を受け取って、現在時刻を自動取得して、で、書き込む。

```putRecordDynamoDB.py
import json
import boto3
import datetime

def lambda_handler(event, context):

    # テーブルを定義
    table_name = 'KanoTable'
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(table_name) 
    
    try:
        strKey = event['queryStringParameters']['Key']
        strMsg = event['queryStringParameters']['Content']
        strNow = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

        # レコード書き出し
        response = table.put_item( 
            Item = { 
                'TargetKey': strKey,
                'TimeStamp': strNow,
                'Content': strMsg
            } 
        )
        return {
            'statusCode': 200,
            'body': json.dumps('Record Added!')
        }
    except:
        print(event) #デバッグ用eventの中身をcloudwatchに出力
        strTemp = "please send two data: ?Key='abc'&Content='def'"
        print(strTemp)
        return {
            'statusCode': 400,
            'body': json.dumps(strTemp)
        }

```

eventのパラメータを得るには、cloudwatchログを眺めて引数で渡した[Key]とか[Content]辺りから見つける。

```
{
'version': 2, 'path': '/default/putRecordDynamoDB', 'httpMethod': 'GET', 

   <中略>

 'queryStringParameters': {'Content': '(^^)', 'Key': 'Ichiro\\'}, 'multiValueQueryStringParameters': {'Content': ['(^^)'], 'Key': ['Ichiro\\']},

   <中略>

 'body': None, 'isBase64Encoded': True
}
```