スイッチIoTで服薬管理 〜M5Stack × AWS〜 [Part1] | 服薬カウンター(2/4)

バックエンド処理の開発

AWS Lambda」を使って、APIルート毎のバックエンド処理をプログラミングします。


1. AWSのLambdaコンソールにサインインして、[関数の作成]ボタンを選択します。



2. 関数の作成画面で、Lambda関数の情報を入力します。

  1. ① 関数名に「medication-management-function」を入力
  2. ② ラインタイムで「Python X.XX」(最新バージョンのPython)を選択
  3. ③ [デフォルトの実行ロールの変更]を開く
  1. ④ [AWS ポリシーテンプレートから新しいロールを作成]を選択
  2. ⑤ ロール名に「medication-management-role」を入力
  3. ⑥ ポリシーテンプレート・オプションで「シンプルなマイクロサービスのアクセス権限」を選択
  4. ⑦ [関数の作成]ボタンを選択
  1. 💡 「シンプルなマイクロサービスのアクセス権限」のポリシーテンプレートでロールを作成することにより、DynamoDBへの基本的なアクセス権限が付与されますが、実は一部の権限が足りません。
    後でロールを編集して「dynamodb:Query」の権限を追加する必要があります。
     ➡ Query権限の追加(最後に詳しく説明します)


3. 関数が作成されたら、コードソースにバックエンド処理をプログラミングしていきます。

  1. ① 「lambda_function.py」に、Pythonでコードを記述する
  2. ② コードが完成したら、[Deploy]ボタンを選択してソースを確定する

今回、服薬管理のバックエンド処理として作成したソースがこちらです。
lambda_function.py
import json
import boto3
from boto3.dynamodb.conditions import Key
from datetime import datetime, timedelta, timezone, date

dynamo = boto3.resource('dynamodb')
table = dynamo.Table('medication-management-table')

def lambda_handler(event, context):
    statusCode = 200
    responseBody = {}

    # ルートキーの取得
    routeKey = event['routeKey']

    try:
        if routeKey == 'PUT /items/{year}/{month}/{day}':
        # 服薬情報登録のPUT APIがコールされた場合

            # パスパラメータ取得
            year = event['pathParameters']['year']         # 年
            month = event['pathParameters']['month']       # 月
            day = event['pathParameters']['day']           # 日

            # パーティションキー(yyyymmdd)の文字列を生成
            yyyymmdd = year + month.zfill(2) + day.zfill(2)
            # ソートキー(entry_datetime)の文字列を生成
            JST = timezone(timedelta(hours=+9), 'JST')
            entry_datetime = datetime.now(JST).strftime('%Y-%m-%d %H:%M:%S')

            # put_itemメソッドでデータ登録
            data = {
                'yyyymmdd' : yyyymmdd,
                'entry_datetime' : entry_datetime
            }
            table.put_item(Item=data)

            responseBody = json.dumps('put ok')

        elif routeKey == 'DELETE /items/{year}/{month}/{day}':
        # 服薬情報削除のDELETE APIがコールされた場合

            # パスパラメータ取得
            year = event['pathParameters']['year']         # 年
            month = event['pathParameters']['month']       # 月
            day = event['pathParameters']['day']           # 日

            # パーティションキー(yyyymmdd)の文字列を生成
            yyyymmdd = year + month.zfill(2) + day.zfill(2)

            # パーティションキー(yyyymmdd)を検索キーとして降順で検索
            data = table.query(
                KeyConditionExpression = Key('yyyymmdd').eq(yyyymmdd),
                ScanIndexForward = False
            )

            # 先頭データのソートキー(entry_datetime)を取得
            entry_datetime = ''
            if data['Count'] > 0:
                entry_datetime = data['Items'][0]['entry_datetime']

            if entry_datetime:
                # 取得したパーティションキーとソートキーをターゲットとして、
                # delete_itemメソッドでデータ削除
                data = {
                    'yyyymmdd' : yyyymmdd,
                    'entry_datetime' : entry_datetime
                }
                res = table.delete_item(Key=data)

            responseBody = json.dumps('delete ok')

        elif routeKey == 'GET /items/{year}/{month}/{day}/{offset}':
        # 服薬情報取得のGET APIがコールされた場合

            # パスパラメータ取得
            year = int(event['pathParameters']['year'])         # 年
            month = int(event['pathParameters']['month'])       # 月
            day = int(event['pathParameters']['day'])           # 日
            offset = int(event['pathParameters']['offset'])     # オフセット

            # 日付計算(オフセット分を加算する)
            key_date = date(year, month, day)
            if offset != 0:
                key_date = key_date + timedelta(days=offset) 

            # YYYYMMDDの文字列に変換
            yyyymmdd = key_date.strftime('%Y%m%d')

            # queryメソッドで指定日付のデータ検索
            # 「ScanIndexForward = False」をつけて降順とする
            data = table.query(
                KeyConditionExpression = Key('yyyymmdd').eq(yyyymmdd),
                ScanIndexForward = False
            )

            # データ件数を取得
            count = data['Count']
            # 先頭データのentry_datetimeを最新登録日付として取得
            lastest_entry = 'None'
            if count > 0:
                lastest_entry = data['Items'][0]['entry_datetime']

            # レスポンスボディに取得結果をJSON形式でセット
            res = {
                'display_date' : key_date.strftime('%Y/%m/%d'),     # 表示日付
                'count' : count,                                    # 件数
                'lastest_entry' : lastest_entry                     # 最新登録日付
            }
            responseBody = json.dumps(res)

    except Exception as e:
        # エラーが起きた時の処理
        print(e)
        statusCode = 500
        responseBody = json.dumps('エラー発生')

    # レスポンス返却
    return {
        'statusCode' : statusCode,
        'body' : responseBody,
        'headers' : {
            'Content-Type': 'application/json'
        }
    }

ポイント解説

服薬情報登録APIの処理(1)
    # ルートキーの取得
    routeKey = event['routeKey']

    try:
        if routeKey == 'PUT /items/{year}/{month}/{day}':
        # 服薬情報登録のPUT APIがコールされた場合
event['routeKey']で、アクセスされたAPIのルートキーが取得できます。
ルートキーが「PUT /items/{year}/{month}/{day}」の場合に、服薬情報登録APIの処理を行います。

服薬情報登録APIの処理(2)
            # パスパラメータ取得
            year = event['pathParameters']['year']         # 年
            month = event['pathParameters']['month']       # 月
            day = event['pathParameters']['day']           # 日

            # パーティションキー(yyyymmdd)の文字列を生成
            yyyymmdd = year + month.zfill(2) + day.zfill(2)
ルートキーの「{year}/{month}/{day}」の部分がパスパラメータとして取得できます。
パスパラメータから年・月・日を取得し、登録するパーティションキー(yyyymmdd)の文字列を生成します。

服薬情報登録APIの処理(3)
from datetime import datetime, timedelta, timezone
  ・・・

            # ソートキー(entry_datetime)の文字列を生成
            JST = timezone(timedelta(hours=+9), 'JST')
            entry_datetime = datetime.now(JST).strftime('%Y-%m-%d %H:%M:%S')
登録するソートキー(entry_datetime)の文字列は、システム日付から生成します。
AWSサーバ上で取得できる時間は協定世界時(UTC)なので、日本標準時(JST)に変換しています。

服薬情報登録APIの処理(4)
import boto3

dynamo = boto3.resource('dynamodb')
table = dynamo.Table('medication-management-table')
  ・・・

            # put_itemメソッドでデータ登録
            data = {
                'yyyymmdd' : yyyymmdd,
                'entry_datetime' : entry_datetime
            }
            table.put_item(Item=data)
boto3ライブラリのput_itemメソッドを使って、パーティションキーとソートキーを登録します。


服薬情報削除APIの処理(1)
        elif routeKey == 'DELETE /items/{year}/{month}/{day}':
        # 服薬情報削除のDELETE APIがコールされた場合
ルートキーが「DELETE /items/{year}/{month}/{day}」の場合に、服薬情報削除APIの処理を行います。

服薬情報削除APIの処理(2)
import boto3
from boto3.dynamodb.conditions import Key

dynamo = boto3.resource('dynamodb')
table = dynamo.Table('medication-management-table')
  ・・・

            # パーティションキー(yyyymmdd)を検索キーとして降順で検索
            data = table.query(
                KeyConditionExpression = Key('yyyymmdd').eq(yyyymmdd),
                ScanIndexForward = False
            )

            # 先頭データのソートキー(entry_datetime)を取得
            entry_datetime = ''
            if data['Count'] > 0:
                entry_datetime = data['Items'][0]['entry_datetime']
削除対象は、パーティションキーで絞り込んだデータのうち、ソートキーの日付が一番新しいレコードとします。(LIFO:後入先出法)
boto3ライブラリのqueryメソッドを使って、パーティションキーを検索キーとして降順でデータ検索します。

例)
2023年04月01日の服薬データに対し、ソートキーで降順に並べて、最初のデータが削除対象となる。
パーティションキー ソートキー
yyyymmdd(服薬した日付) entry_datetime(記録した日時)
20230401 2023-04-01 15:04:12
20230401 2023-04-01 09:22:03
20230401 2023-04-01 09:21:36

服薬情報削除APIの処理(3)
import boto3

dynamo = boto3.resource('dynamodb')
table = dynamo.Table('medication-management-table')
  ・・・

            if entry_datetime:
                # 取得したパーティションキーとソートキーをターゲットとして、
                # delete_itemメソッドでデータ削除
                data = {
                    'yyyymmdd' : yyyymmdd,
                    'entry_datetime' : entry_datetime
                }
                res = table.delete_item(Key=data)
boto3ライブラリのdelete_itemメソッドを使って、パーティションキーとソートキーに該当するデータを削除します。(データが登録されていない場合は、削除しない)


服薬情報取得APIの処理(1)
elif routeKey == 'GET /items/{year}/{month}/{day}/{offset}':
        # 服薬情報取得のGET APIがコールされた場合
ルートキーが「GET /items/{year}/{month}/{day}/{offset}」の場合に、服薬情報取得APIの処理を行います。

服薬情報取得APIの処理(2)
from datetime import timedelta, date
  ・・・

            # パスパラメータ取得
            year = int(event['pathParameters']['year'])         # 年
            month = int(event['pathParameters']['month'])       # 月
            day = int(event['pathParameters']['day'])           # 日
            offset = int(event['pathParameters']['offset'])     # オフセット

            # 日付計算(オフセット分を加算する)
            key_date = date(year, month, day)
            if offset != 0:
                key_date = key_date + timedelta(days=offset) 

            # YYYYMMDDの文字列に変換
            yyyymmdd = key_date.strftime('%Y%m%d')
パスパラメータから年・月・日と「オフセット」を取得します。
オフセットは、当日では「0」、前日では「-1」、翌日では「1」が渡されます。
年月日にオフセット分を加算して、データ取得する日付を求めています。

服薬情報取得APIの処理(3)
import boto3
from boto3.dynamodb.conditions import Key

dynamo = boto3.resource('dynamodb')
table = dynamo.Table('medication-management-table')
  ・・・

            # queryメソッドで指定日付のデータ検索
            # 「ScanIndexForward = False」をつけて降順とする
            data = table.query(
                KeyConditionExpression = Key('yyyymmdd').eq(yyyymmdd),
                ScanIndexForward = False
            )

            # データ件数を取得
            count = data['Count']
            # 先頭データのentry_datetimeを最新登録日付として取得
            lastest_entry = 'None'
            if count > 0:
                lastest_entry = data['Items'][0]['entry_datetime']
boto3ライブラリのqueryメソッドを使って、パーティションキーを検索キーとして降順でデータ検索します。
queryメソッドの結果から、データ件数と最新登録日付を取得しています。

服薬情報取得APIの処理(4)
import json
  ・・・

    statusCode = 200
    responseBody = {}

  ・・・

            # レスポンスボディに取得結果をJSON形式でセット
            res = {
                'display_date' : key_date.strftime('%Y/%m/%d'),     # 表示日付
                'count' : count,                                    # 件数
                'lastest_entry' : lastest_entry                     # 最新登録日付
            }
            responseBody = json.dumps(res)

  ・・・

    # レスポンス返却
    return {
        'statusCode' : statusCode,
        'body' : responseBody,
        'headers' : {
            'Content-Type': 'application/json'
        }
    }
取得結果をJSON形式にして、レスポンスボディで返却します。


■Query権限の追加

「シンプルなマイクロサービスのアクセス権限」のポリシーテンプレートを選択してLambda関数を作成しましたが、このポリシーテンプレートには 「dynamodb:Query」の権限が付与されていません。
今回のソースではqueryメソッドでDynamoDBにアクセスしているため、ロールを編集してDynamoDBのQuery権限を追加しましょう。

1. AWSのIAMマネジメントコンソールにサインインし、ロールの編集を行います。

  1. ① [ロール]メニューを選択し、ロール画面を開く
  2. ② Lambda関数の作成時に設定したロールを選択


2. ロールの概要画面で、「AWSLambdaMicroserviceExecutionRole」のポリシー名を選択します。



3. ポリシーの詳細画面で、「許可」の[編集]ボタンを選択します。



4. 許可の編集画面で、ポリシーに「"dynamodb:Query"」の権限を追加します。

  1. ① ポリシーエディタでJSONに「"dynamodb:Query",」を追加
  2. ② [次へ]ボタンを選択


5. 確認と保存画面で、[変更を保存]ボタンを選択します。


あなたへのおすすめ記事