【サーバレス】API GatewayとLambda(Python)を使ってDynamoDBへ読書きをする

今回も何番煎じか分かりませんが以下AWSハンズオンの構成をお試し構築していきます。本件は私のようなサーバレス構成に慣れていない方でもできる比較的簡単な内容（だと思います）です。

[チュートリアル: Lambda と DynamoDB を使用して CRUD HTTP API を作成する](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-dynamo-db.html)

# 今回のゴール
PCからの指定URLへのHTTPリクエストで、**サーバレスにDynamoDB への書き込みとDynamoDB からのデータ読み込み**ができるようにする。構成は以下のようなイメージです。

![スライド22.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/6c544a7b-7b91-1a6f-8f95-5089fc059dac.png)

ちなみに「CRUD」とは…

> CRUDとは、データベース管理システム（DBRS）に必要とされる4つの主要な機能、「作成（Create）」「読み出し（Read）」「更新（Update）」「削除（Delete）」をそれぞれ頭文字で表したもののことである。

だそうです。

【出典】

https://japan.zdnet.com/glossary/exp/CRUD/?s=4#:~:text=CRUD%E3%81%A8%E3%81%AF%E3%80%81%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E7%AE%A1%E7%90%86,%E3%81%97%E3%81%9F%E3%82%82%E3%81%AE%E3%81%AE%E3%81%93%E3%81%A8%E3%81%A7%E3%81%82%E3%82%8B%E3%80%82




# 前提条件(+免責事項)
- 当方では動作保証はしていません。何か質問があればAWSサポートまで。
- リソースへのアクセス許可設定のためIAMロールなどのAWS権限設定できるIAMユーザアカウントが必要になります。
- Lambdaは記載時最新の<font color="Red">**Python3.13**</font>で記載します。
- リソース利用で料金がかかります。環境構築後に本環境が不要になった場合、リソースの削除をお忘れなく。



## HTTP APIとRest APIの違い
他の方の記事、ブログなどでRest APIで構築されている場合もありますが本記事はハンズオン資料に沿って、HTTP APIで設定します。HTTP APIとRest APIの違いですがこんな感じです。

| Rest API | HTTP API |
| :---: | :---: |
| AWS WAF利用OK | AWS WAF利用**NG** |
| 相互認証TLS利用OK | 相互認証TLS利用**NG** |
| カスタムドメイン利用OK | カスタムドメイン利用OK |
| 費用：標準 | 費用：低め |

[参照]
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-vs-rest.html



# 設定の流れ
## ①DynamoDBのテーブル作成
まずはデータベースとなるDynamoDBを構築していきます。

今回テーブル名は「http-crud-tutorial-items」、パーティションキーは「id」で登録します。
ソートキーは設定なしで、テーブル設定は「デフォルト」で設定します。

![スライド1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/9a157613-3689-4c60-06f9-0cad0c5a2459.png)

これでDynamoDBのテーブルを作成します。





## ②Lambda関数の設定
次にLambda関数の設定を行います。今回はテンプレートは使わないので「一から作成」を選択し、関数名には「http-crud-tutorial-function」と入力します。ランタイムはPython 3.13を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/20de7366-7f38-559c-f5ee-667c56113d53.png)


デフォルトの実行ロールの変更で「AWSポリシーテンプレートから新しいロールを作成」を選択し、ロール名には「http-crud-tutorial-role」を、ポリシーテンプレートでは「シンプルなマイクロサービスのアクセス権限」を選択し、作成します。

![スライド2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/807dd737-b21a-17f7-6a28-c3fb5300407c.png)

関数の作成が完了したらPythonコードを記述していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/adea2e81-8568-18c6-8a01-bcc05e524d28.png)

記述内容は下記です。
```
import json
import boto3
from decimal import Decimal

client = boto3.client('dynamodb')
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table('http-crud-tutorial-items')
tableName = 'http-crud-tutorial-items'


def lambda_handler(event, context):
    print(event)
    body = {}
    statusCode = 200
    headers = {
        "Content-Type": "application/json"
    }

    try:
        if event['routeKey'] == "DELETE /items/{id}":
            table.delete_item(
                Key={'id': event['pathParameters']['id']})
            body = 'Deleted item ' + event['pathParameters']['id']
        elif event['routeKey'] == "GET /items/{id}":
            body = table.get_item(
                Key={'id': event['pathParameters']['id']})
            body = body["Item"]
            responseBody = [
                {'price': float(body['price']), 'id': body['id'], 'name': body['name']}]
            body = responseBody
        elif event['routeKey'] == "GET /items":
            body = table.scan()
            body = body["Items"]
            print("ITEMS----")
            print(body)
            responseBody = []
            for items in body:
                responseItems = [
                    {'price': float(items['price']), 'id': items['id'], 'name': items['name']}]
                responseBody.append(responseItems)
            body = responseBody
        elif event['routeKey'] == "PUT /items":
            requestJSON = json.loads(event['body'])
            table.put_item(
                Item={
                    'id': requestJSON['id'],
                    'price': Decimal(str(requestJSON['price'])),
                    'name': requestJSON['name']
                })
            body = 'Put item ' + requestJSON['id']
    except KeyError:
        statusCode = 400
        body = 'Unsupported route: ' + event['routeKey']
    body = json.dumps(body)
    res = {
        "statusCode": statusCode,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": body
    }
    return res
```



記述が完了したら「DEPLOY」を行います。



## ③API Gatewayの作成
APIタイプでは「HTTP API」を選択します。

![スライド3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/ca54239c-cc01-cc24-14cf-7954ff439bfa.png)

API名は「http-crud-tutorial-api」とします。

![スライド4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/5ed59148-259d-6903-4b61-95f9438b1a29.png)

いったんルートは設定せずに、「次へ」をクリックします。

![スライド5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/1043551a-6519-a8f8-bb64-82de378bf159.png)

ステージ名は「$default」のままでOKなので、「次へ」をクリックし、APIを作成します。
![スライド6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/22d1ae9e-0689-aa72-d24e-e0cb327b2767.png)

APIが作成されたら、左側のメニューから「Develop」-「Route」を選択し、「作成」をクリックします。
![スライド7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/7b686ee6-877d-e3a3-a984-6bdf567cf672.png)


以下の４つのルートを作成します。
| メソッド | パス |
| :---: | :---: |
| GET | /items/{id} |
| GET | /items |
| PUT | /items |
| DELETE | /items/{id} |

 

 

 

 作成するとこんな感じになります。

![スライド8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/15c46863-c130-6523-c5b1-ed3a9ea64219.png)

![スライド9.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/8d329dd9-a4dc-f051-a60c-a2cdf7877890.png)



## ④API GatewayとLambda関数の紐づけ
続いて先ほど作成したAPIとLambdaの紐づけを行っていきます。APIの管理画面で左側メニューの「Develop」-「Integrations」を選択し、「統合を管理」から「Create」をクリックします。

![スライド10.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/7484000f-8254-f182-43c7-8043fa5aec7c.png)


先ほど作成した４つのルートすべてに②で作成したLambda関数を紐づけしていきます。
![スライド11.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/cefad4d2-5ce2-6b00-02cd-3ce4b0e41d14.png)

設定するとこんな感じになります。
![スライド14.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/85ba8085-e9af-dba5-315e-46d332c601e3.png)

APIとLambdaの紐づけが完了するとLambda側でも下記のように画面に紐づけが反映されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/836d56f0-a0d4-97c8-c504-224ac4d2ba29.png)


これで環境構築は完了です。

## ⑤動作確認

それではちゃんと動作するか確認していきましょう。APIの管理画面の左側メニューの真ん中付近に「API: http-crud-tutor・・・」があるので、そこをクリックします。

![スライド15.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/5c84b59b-4d0e-8bd6-01f0-cf4073b06cae.png)

「http-crud-tutorial-api のステージ (1)」の下に書かれているURLがAPIにアクセスするURLになります。


PCのコマンドプロンプトで実行してもいいですがせっかくなのでAWS CloudShellで実行したいと思います。コンソールの右上の再生マークの表示があると思うのでクリックしてもらえれば、CloudShellが起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/76221cd0-c909-1718-d3e0-4d241a256f21.png)

CloudShellが起動したら以下のコマンドを実行します。このコマンドはHTTPアクセスのPUTメソッドでid=123で、price=1234で、name=myitemというデータを書き込みする内容になっています。

```
curl -X "PUT" -H "Content-Type: application/json" -d "{\"id\": \"123\", \"price\": 12345, \"name\": \"myitem\"}" https://｛「http-crud-tutorial-api のステージ (1)」の下に書かれているURL｝
```

正常に動作した場合はDynamoDBに以下のようにデータが書き込みされます。

![スライド16.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/e866e80b-2b4c-08e8-b682-e85cdcf838fe.png)

次にid=123のデータについて読み取りを行います。
```
curl https://{「http-crud-tutorial-api のステージ (1)」の下に書かれているURL}/items/123
```
先ほど登録したデータが読み取れればOKです。ちなみにPCのコマンドプロンプトで実行すると以下のようなレスポンスが表示されます。

```
PS C:\Users\administrator> curl https://{「http-crud-tutorial-api のステージ (1)」の下に書かれているURL}/items/123


StatusCode        : 200
StatusDescription : OK
Content           : [{"price": 12345.0, "id": "123", "name": "myitem"}]
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Apigw-Requestid: XXXXXXXXXXX
                    Content-Length: 51
                    Content-Type: application/json
                    Date: Mon, 13 Jan 2025 05:17:56 GMT

                    [{"price": 12345.0, "id": "123", ...
Forms             : {}
Headers           : {[Connection, keep-alive], [Apigw-Requestid, XXXXXXXXX=], [Content-Length, 51], [Content-Type
                    , application/json]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 51

```


# おわりに
AWSハンズオンには難解はものもありますが、このハンズオンはシンプルで結果も分かりやすいのサーバレス初心者にも優しくて助かります。何となくできるSEになった気分になれますね笑
。

# 参考にしたサイト

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-dynamo-db.html


https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-vs-rest.html