Django REST Frameworkでdynamodbを操作するREST APIを作成する

#はじめに
django REST Frameworkとboto3でAWSのDynamoDBに対して操作を行うapiを作成する。
GET、POST、PUT、DELETEの操作ができるようにする。

# Dynamodbテーブル作成(事前準備)
下記のようなテーブルを事前に用意し、いくつかデータを入れておく
テーブル名: Fruits
hash key: Name

<img width="1427" alt="スクリーンショット 2020-08-10 22.16.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/018f984d-29cd-7495-2388-69ba648c4a7f.png">

# djangoプロジェクトの作成
django project(dynamo_operation)とapp(api)を作成

```shell
$ django-admin startproject dynamo_operation
$ cd dynamo_operation/
$ django-admin startapp api 
```

# setting.pyの編集
`setting.py`に`rest_framework`と先ほど作成した`app`のconfigを追加する。

```python:dynamo_operation/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', #追加
    'api.apps.ApiConfig', #追加
]
```

# DynamoDBリクエスト用のmodelを作成
djangoではDBの作成、操作に`model`を用意する。
DynamoDBへのリクエストはboto3を使用するので特に`model`は必要ないが、今回は`model(dynamo_model.py)`を用意した。

```python:api/dynamo_model.py
class Fruit():
    def __init__(self, name):
        self.name = name
```

# views.pyの編集

```python:api/views.py
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response 
from api.dynamo_models import Fruit
from boto3 import client

dynamodb_client = client('dynamodb')

class DynamoRequest(APIView):
    # 全体GET
    def get(self, request):
        response = []
        items = dynamodb_client.scan(TableName='Fruits')['Items']
        for item in items:
            fruit = Fruit(item['Name']['S'])
            fruit.price = item.get('Price',{}).get('N', '')
            response.append(fruit.__dict__)
        return Response(response)
    
    def post(self, request):
        request_data = request.data
        item = {'Name': {'S': request_data['Name']}}

        if 'Price' in request_data:
            item['Price'] = {'N': request_data['Price']}

        dynamodb_client.put_item(
            TableName = 'Fruits',
            Item = item
        )
        return Response(status=status.HTTP_201_CREATED)


class DynamoDetailRequest(APIView):
    #単体GET
    def get(self, request, pk):
        item = dynamodb_client.get_item(
            TableName = 'Fruits',
            Key = {
                'Name': {'S': pk},
            }
        )['Item']
        fruit = Fruit(item['Name']['S'])
        fruit.price = item.get('Price',{}).get('N', '')
        return Response(fruit.__dict__)
    
    def put(self, request, pk):
        request_data = request.data

        item = dynamodb_client.get_item(
            TableName = 'Fruits',
            Key = {
                'Name': {'S': pk},
            }
        )['Item']

        price = item.get('Price',{}).get('N', '0')

        if 'Price' in request_data:
            price = request_data['Price']

        dynamodb_client.put_item(
            TableName = 'Fruits',
            Item = {
                'Name': {'S': item['Name']['S']},
                'Price': {'N': price}
            }
        )
        return Response(status=status.HTTP_200_OK)
    
    def delete(self, request, pk):
        dynamodb_client.delete_item(
            TableName = 'Fruits',
            Key = {
                'Name': {'S': pk},
            }
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
```

`rest_framework`の`APIView`を継承した`class`でリクエストを処理する。
`DynamoRequest`がpathパラメータなしのリクエストを処理し、`DynamoDetailRequest`でpathパラメータ(pk)ありのリクエストの処理を行う。
`APIView`を継承することにより、HTTPメソッドごとにfunctionを用意することでそれぞれのメソッドに対応する処理を追加することができる。

# urls.pyの編集
```python:api/urls.py
from django.urls import path
from api import views

urlpatterns = [
    path('api/', views.DynamoRequest.as_view()),
    path('api/<pk>/', views.DynamoDetailRequest.as_view())
]
```
`dynamo_opration`フォルダの`urls.py`も編集する

```python:dynamo_opration/urls.py
from django.urls import path, include

urlpatterns = [
    path('', include('api.urls')),
]
```

# curlコマンドで動作確認
## serverの起動
```shell
$ python manage.py runserver
```

## GET(全体検索)
```shell
$ curl http://127.0.0.1:8000/api/

# レスポンス
[{"name":"orange","price":"200"},{"name":"banana","price":"100"},{"name":"apple","price":"100"}]
```

## POST

```shell
$ curl -X POST \
  -H 'Content-Type:application/json' \
  -d '{"Name": "peach", "Price": "400"}' \
  http://127.0.0.1:8000/api/
```
POSTリクエスト後のテーブル
<img width="1247" alt="スクリーンショット 2020-08-10 23.16.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/969cf6be-fe9c-37f2-b58a-c903eb6a83d3.png">

peachの項目が追加されている。

## GET（単体）
appleの項目を取得

```shell
$ curl http://127.0.0.1:8000/api/apple/

# レスポンス
{"name":"apple","price":"100"}
```

## PUT
appleのpriceを100 -> 200へ変更する

```shell
$ curl -X PUT \
  -H 'Content-Type:application/json' \
  -d '{"Price": "200"}' \
  http://127.0.0.1:8000/api/apple/
```

PUTリクエスト後のテーブル
<img width="1251" alt="スクリーンショット 2020-08-10 23.21.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/d7d558a9-d55f-012b-a992-1f0d149f7bb7.png">

## DELETE
peachの項目を削除する。

```shell
$ curl -X DELETE http://127.0.0.1:8000/api/peach/
```

DELETEリクエスト後のテーブル
<img width="1253" alt="スクリーンショット 2020-08-10 23.23.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/88903a4b-d447-ccb8-a359-9f96398c9dd1.png">

# おわりに
django REST Framework + boto3でDynamoDBの操作を行うREST Apiを作成した。
今回は、dynamodb_model.pyを用意してmodelを管理するようにしたが、必要なかったかもしれない（この辺の設計は今後改善していきたい）。