AWS Lambda関数(画像アップロード、S3へのファイル配置、DynamoDB更新など)

本エントリでは作成したアプリで使用するAPIの機能を実現するLambda関数について記載します。
目的のアプリには主に3つ
画像の取得とS3への配置(画像データの取得とファイル作成)
アップロードされたデータの参照(一覧用データの出力)
目的のデータの更新(イイネ機能)

以上の三つを実現するためにそれぞれの目的用に三つのLambda関数を作成しました。順に紹介していきます。
画像の取得とS3への配置(画像データの取得とファイル作成)

```
import json
import boto3
import base64
from datetime import datetime
from boto3.dynamodb.conditions import Key, Attr

s3 = boto3.resource('s3')      # ③S3オブジェクトを取得

def lambda_handler(event, context):
    print('START LOG')
    print(json.dumps(event['body-json']))

    try:
        s3 = boto3.resource('s3')
        bucket = s3.Bucket('XXXX-XXXX')

        dynamodb = boto3.resource('dynamodb')
        table    = dynamodb.Table('XXXXXXXX')

        imageBody = base64.b64decode(event['body-json']['base64'])
        TextBody = event['body-json']['Text']
        TextTime = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')

        key = 'test_' + TextTime + '.jpg'

        bucket.put_object(
            Body = imageBody,
            Key = key
        )

        table.put_item(
            Item={
                'datetimestr': TextTime,
                'PictureFile': key,
                'LikeNum':0,
                'Comment':TextBody
           }
        )


        print('success!')
    except Exception as e:
        print('Error!')
        print(e)
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda! ')
    }
```
最初にここではファイル配置というS3へのアクセスと画像データの登録というDynamoDBへのアクセスを行います。S3へのアクセスはpythonでLambda関数を作るには必須の
`import base64`
だけで可能ですが、DynamoDBを使う場合は加えて
`from boto3.dynamodb.conditions import Key, Attr`
を読み込んでおいて下さい。またロールに
`AmazonDynamoDBFullAccess`
というポリシーを加えておいて下さい。

アプリ側からの送信データとeventのキーが
`event['body-json']['base64']`
というように一致しているかとおもいます。この辺最初僕も割と悩みましたが別記事に記載するAPI Gatewayの設定を素直にしてやるとシンプルに送られてきたデータを取得することが出来ます。ここでは画像データ(base64)と入力されたコメント(Text)を取得しています。
ファイル名とデータを管理するDynamoDBに作成するテーブルのパーティションキーとなる文字列は時間を元に作成するので先にその時点の時刻で文字列を組み立ててファイル名とパーティションキーに挿入する変数に使用しています。
まず画像データをS3に配置するのはものすごくシンプルでbase64をデコードしてバイナリに戻しファイル名(key)をセットしてput_objectを実行するだけです。バケットのどこか別のフォルダ(という言い方は正確ではないでしょうが)に配置したい時はこのファイル名の頭にパスを指定するようです。
次のDynamoDBへの登録については挿入についてはすごくシンプルでこれも一関数のみで完了します。尚、別の記事にも記載しますがDynamoDB(NOSQL系全般そうなのでしょうか?)テーブル設計時はキー項目しか設定しないので(ここではdatetimestrのみ)謂わばこの挿入のタイミングでテーブルの構造が決まるとも言えます。

以上がまず画像の取得とS3への配置用のLambda関数でした。

アップロードされたデータの参照(一覧用データの出力)

```
import json
import boto3
from datetime import datetime
from boto3.dynamodb.conditions import Key, Attr

s3 = boto3.resource('s3')      # ③S3オブジェクトを取得
# ------------------------- DECIMAL FORMAT ------------------------------

def lambda_handler(event, context):
    # TODO implement

    dynamodb = boto3.resource('dynamodb')
    table    = dynamodb.Table('XXXXXX')

    res = table.scan()

    bodydic = []
        picurl = 'https://s3-XXXXXXX.amazonaws.com/XXXX-XXXX/' + i['PictureFile']
        for i in res['Items']:
        dic = {'datetimestr':i['datetimestr'],'PictureFile':picurl,'LikeNum':i['LikeNum'],'Comment':i['Comment']}
        bodydic.append(dic)

    return {
        'isBase64Encoded': False,
        'statusCode': 200,
        'headers': {},
        'body':bodydic
    }
```
非常にシンプルです。
`res = table.scan()`
はテーブルの全件取得です。それほど件数が来ることを想定していないので特に絞り込みをかけていません。
DynamoDBの返却値は'Items'とし格納されているようです。
取得したデータを後にJSONとして出力できるようにキーと値の形で辞書型に格納していっています。後の簡単のためにS3へ画像を参照するURLをここで組み立てています。
出来上がった辞書をbodyに入れてそのまま出力しています。


目的のデータの更新(イイネ機能)

```
import json
import boto3
from boto3.dynamodb.conditions import Key, Attr

def lambda_handler(event, context):
    # TODO implement
    dynamodb = boto3.resource('dynamodb')
    table    = dynamodb.Table('FirstPicAndItem')

    print(json.dumps(event))

    response = table.query(KeyConditionExpression=Key('datetimestr').eq(event['body-json']['datetimestr']))
    print(response)

    for i in response['Items']:
        likenum = i['LikeNum']
    likenum+=1

    print('likenum:'+str(likenum))

    response = table.update_item(
        Key= {
            'datetimestr': event['body-json']['datetimestr']
            },
        UpdateExpression="ADD #name :increment",
        ExpressionAttributeNames={
            '#name':'LikeNum'
            },
        ExpressionAttributeValues={
            ":increment": 1
            },
        ReturnValues="UPDATED_NEW"
    )

    return {
        'statusCode': 200,
        'body': json.dumps('Increment Success!!')
    }
```
まず本Lambda関数はイイネ数をインクリメントする為のものです。
`event['body-json']['datetimestr']`
でアプリから渡されたイイネ数をインクリメントする対象のdatetimestrを取得。
次にその値でテーブルに参照に行きます。テーブルのdatetimestrはハッシュキーにしているのでqueryで絞り込めます。
queryの結果として返却された値response['Items']はdatetimestrがユニークな値なので一件しか無いはずです。一件しかないはずですがfor in で取り出すのが流儀のようなので回します。ここでは'LikeNum'の値が必要なので取得します。
次にこのLikeNumの値をインクリメントし結果をDynamoDBに反映します。DynamoDBの更新は(RDBの感覚で言うと)更新対象のレコードを指定するのにかなり制約がありハッシュキーかパーティションキーとソートキーとの複合条件で行うようです(逆に言うとこういう制約を考慮するのがテーブル設計の勘所のようです)。ここではdatetimestrをハッシュキーとしているのでdatetimestrのみで更新をかけます。
ここで困ったのはUpdateExpressionのADDでこれは数値型にしか使えません。SETなら文字型でも使えるようですが…割と手こずったところでした。

以上で作成した三つのLambda関数の説明を閉じさせて頂きたいと思います。