注文 API を構築して検証する

AWSBeginner
オンラインで実践に進む

はじめに

注文チームは、JSON の注文を受け付け、計算した合計を返す必要があります。このラボでは、ヘルスチェック、書き込み、読み取りのルートを、提供された Lambda アプリケーションに接続します。有効なリクエストと拒否されるリクエストをテストし、削除する前に、保存された DynamoDB のレコードを確認します。

最初に HTTP API で Lambda 関数を公開する と Lambda から DynamoDB を読み書きする を完了してください。この新しい環境には、アプリケーション、ロール、独立したテーブルが用意されています。

認定試験との関連

認定資格 試験タスク 演習内容
Solutions Architect – Associate (SAA-C03) タスク 2.1 API リクエスト検証、レスポンス、保存された業務データ。
Developer – Associate (DVA-C02) タスク 1.1, タスク 3.2 API リクエスト検証、レスポンス、保存された業務データ。
DevOps Engineer – Professional (DOP-C02) タスク 3.2 基礎演習:API リクエスト検証、レスポンス、保存された業務データ。

ラボの概要

概念図:HTTP のヘルスルートと注文ルートが Lambda バックエンドを共有し、アプリケーションが JSON を検証して有効な注文を DynamoDB に保存または読み取ります。

注文のバックエンドを準備する

このステップでは、提供されたリクエストの仕様を確認し、関数に注文テーブルへのアクセスを許可して、バックエンドをデプロイします。アプリケーションは、整数のセント単位で、数量 × 250 + 100 セントの手数料を計算します。上部の AWS View を使い、リクエストと保存データを比較してください。

作業ディレクトリに移動し、アプリケーションを読みます。

cd /home/labex/project
cat app.py

handler は HTTP ペイロード形式 2.0 を受け取ります。POST /orders は、正確に 2 つの JSON フィールドを要求します。id は小文字の 2~40 文字で、英字で始まり、英字、数字、ハイフンを使います。quantity は 1~10 の整数です。コードは PutItem の前に、それ以外の形式を拒否します。GET /orders?id=... は、GetItem で保存されたレコードを読み取ります。各レスポンスには、HTTP ステータスと JSON 本文が含まれます。提供されたアプリケーションを書き直す必要はありません。

準備済みのテーブル一覧を読み取ります。

aws dynamodb list-tables --query TableNames

labex-a02-orders と labex-a02-reference が表示されるはずです。この API の業務処理に属するのは、注文テーブルだけです。その ARN をシェル変数に読み込みます。

TABLE_ARN=$(aws dynamodb describe-table \
  --table-name labex-a02-orders \
  --query Table.TableArn \
  --output text)
echo "$TABLE_ARN"

実行ロールは、関数からデータベースへのアクセスを制御します。ヒアドキュメントでポリシーファイルを作成します。引用符のない JSON マーカーにより、$TABLE_ARN は読み取った ARN に展開されます。認証情報は含まれていません。

cat > orders-policy.json <<JSON
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["dynamodb:PutItem", "dynamodb:GetItem"],
    "Resource": "$TABLE_ARN"
  }]
}
JSON

この名前付きのインラインポリシーを、準備済みの実行ロールに付けます。

aws iam put-role-policy \
  --role-name labex-a02-execution \
  --policy-name OrdersData \
  --policy-document file://orders-policy.json

アーカイブの最上位に app.py を置いて、提供されたコードをパッケージ化します。

zip orders.zip app.py

準備済みのロール ARN を読み取ります。

ROLE_ARN=$(aws iam get-role \
  --role-name labex-a02-execution \
  --query Role.Arn \
  --output text)
echo "$ROLE_ARN"

10 秒のタイムアウトで関数をデプロイします。アプリケーションは、環境から TABLE_NAME を読み取り、テーブルの選択をソースコードから分離します。

aws lambda create-function \
  --function-name labex-a02-orders-api \
  --runtime python3.12 \
  --handler app.handler \
  --role "$ROLE_ARN" \
  --timeout 10 \
  --environment '{"Variables":{"TABLE_NAME":"labex-a02-orders"}}' \
  --zip-file fileb://orders.zip \
  --query '{Name:FunctionName,Handler:Handler,Table:Environment.Variables.TABLE_NAME}'

関数名、app.handler、labex-a02-orders が返るはずです。AWS View で Function カードを確認し、注文テーブルのアイテム数がまだゼロであることを確認します。コードのデプロイとデータアクセスの許可では、注文は作成されません。

ヘルスルートと注文ルートを対応付ける

このステップでは、3 つのルートに 1 つの統合を設定し、各ルートに関数の呼び出しを許可します。

HTTP API を作成し、その ID を保存します。

API_ID=$(aws apigatewayv2 create-api \
  --name labex-a02 \
  --protocol-type HTTP \
  --query ApiId \
  --output text)
echo "$API_ID"
printf '%s\n' "$API_ID" > api-id.txt

文字どおりの $default ステージを、自動デプロイで作成します。

aws apigatewayv2 create-stage \
  --api-id "$API_ID" \
  --stage-name '$default' \
  --auto-deploy \
  --query '{Stage:StageName,AutoDeploy:AutoDeploy}'

バックエンド ARN を読み取り、ペイロード 2.0 の統合を作成します。その POST メソッドは Lambda を呼び出すためのものです。各クライアントのルートには、引き続きそれぞれの HTTP メソッドがあります。

FUNCTION_ARN=$(aws lambda get-function-configuration \
  --function-name labex-a02-orders-api \
  --query FunctionArn \
  --output text)
echo "$FUNCTION_ARN"
INTEGRATION_ID=$(aws apigatewayv2 create-integration \
  --api-id "$API_ID" \
  --integration-type AWS_PROXY \
  --integration-method POST \
  --integration-uri "$FUNCTION_ARN" \
  --payload-format-version 2.0 \
  --query IntegrationId \
  --output text)
echo "$INTEGRATION_ID"

公開のヘルスエンドポイントを、この統合に対応付けます。

aws apigatewayv2 create-route \
  --api-id "$API_ID" \
  --route-key 'GET /health' \
  --target "integrations/$INTEGRATION_ID" \
  --authorization-type NONE \
  --query RouteKey \
  --output text

JSON の書き込みルートを対応付けます。

aws apigatewayv2 create-route \
  --api-id "$API_ID" \
  --route-key 'POST /orders' \
  --target "integrations/$INTEGRATION_ID" \
  --authorization-type NONE \
  --query RouteKey \
  --output text

保存された注文の読み取りルートを対応付けます。

aws apigatewayv2 create-route \
  --api-id "$API_ID" \
  --route-key 'GET /orders' \
  --target "integrations/$INTEGRATION_ID" \
  --authorization-type NONE \
  --query RouteKey \
  --output text

実行ロールのデータベースポリシーは、API Gateway に関数の呼び出しを許可しません。アカウント ID を読み取り、3 つの正確な呼び出し許可を追加します。各ステートメントは、API ID、文字どおりの $default、クライアントのメソッド、ルートパスを使います。\$ は、展開するシェル文字列内でドル記号を保持します。

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo "$ACCOUNT_ID"
aws lambda add-permission \
  --function-name labex-a02-orders-api \
  --statement-id ApiHealth \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-account "$ACCOUNT_ID" \
  --source-arn "arn:aws:execute-api:us-east-1:$ACCOUNT_ID:$API_ID/\$default/GET/health" \
  --query Statement \
  --output text
aws lambda add-permission \
  --function-name labex-a02-orders-api \
  --statement-id ApiWriteOrder \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-account "$ACCOUNT_ID" \
  --source-arn "arn:aws:execute-api:us-east-1:$ACCOUNT_ID:$API_ID/\$default/POST/orders" \
  --query Statement \
  --output text
aws lambda add-permission \
  --function-name labex-a02-orders-api \
  --statement-id ApiReadOrder \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-account "$ACCOUNT_ID" \
  --source-arn "arn:aws:execute-api:us-east-1:$ACCOUNT_ID:$API_ID/\$default/GET/orders" \
  --query Statement \
  --output text

生成された ID を使い、準備済みの作業環境の API アドレスを作成します。このアドレスは作業環境内でリクエストを処理するもので、AWS の公開ホスト名ではありません。

API_URL="http://127.0.0.1:8081/api/$API_ID"
echo "$API_URL"

ヘルスルートをテストします。curl -i は、HTTP レスポンスのヘッダーを含めます。

curl -i "$API_URL/health"

HTTP 200 と {"healthy": true} が返るはずです。AWS View で、3 つのルートキーがすべて NONE、ステージが AutoDeploy 有効で、ヘルスチェックの呼び出しが 200 を返したことを確認します。このリクエストは注文の保存ではなくヘルスチェックなので、注文テーブルはまだ空のはずです。

3 つのルートが 1 つのハンドラーを共有し、注文の書き込みと読み取りは注文テーブルを使いますが、ヘルスチェックは使いません。

有効な注文と拒否されるリクエストをテストする

このステップでは、2 件の注文を作成し、保存された結果を読み取り、失敗したリクエストが業務データに影響しないことを確認します。

HTTP リクエストは、メソッド、パス、ヘッダー、任意の本文で構成されます。-X POST は書き込みメソッドを選び、-H は Content-Type: application/json を渡し、--data は JSON 文字列を送ります。数量 2 の注文を作成します。

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"order-one","quantity":2}'

HTTP 201 Created と、order-one、数量 2、total_cents: 600(2 × 250 + 100)を含む本文が返るはずです。ステータスは、注文が保存されたことをクライアントに伝え、本文は計算結果を報告します。

異なる数量で、2 件目の注文を作成します。

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"order-two","quantity":5}'

HTTP 201 と total_cents: 1350 が返るはずです。クエリパラメータを使い、API を通じて両方の注文を読み取ります。

curl -i "$API_URL/orders?id=order-one"
curl -i "$API_URL/orders?id=order-two"

両方のレスポンスは HTTP 200 で、対応する作成レスポンスと同じ ID、数量、合計になるはずです。読み取りハンドラーは、新しいクライアントの数量から合計を再計算するのではなく、DynamoDB の状態を取得します。

CLI で、最初の保存されたアイテムを独立して確認します。

aws dynamodb get-item \
  --table-name labex-a02-orders \
  --key '{"id":{"S":"order-one"}}' \
  --consistent-read \
  --query Item

型付きアイテムには、quantity: {"N":"2"} と total_cents: {"N":"600"} が含まれます。AWS View で、実際の 2 件のテーブルレコードと、POST および GET の呼び出しレスポンスを比較します。

構文上は有効な JSON リクエストでも、業務の仕様に違反することがあります。負の数量を送ります。

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"bad-negative","quantity":-1}'

HTTP 400 Bad Request と Use an id and an integer quantity from 1 to 10 が返るはずです。JSON 文字列で表した数値も拒否されます。

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"bad-type","quantity":"2"}'

最後に、形式の壊れた JSON 本文をテストします。

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{not-json'

最後の 2 つのリクエストも、HTTP 400 を返します。実際のハンドラーイベントが CloudWatch Logs に表示されることはありますが、テーブルには有効な 2 件の注文だけが残っていなければなりません。リクエストの検証は、提供されたアプリケーション内で、PutItem の前に行われます。API ルートが一致しただけでは、本文は検証されません。

読み取り ID が有効でも、保存された注文がないことがあります。まだ作成していない注文をクエリします。

curl -i "$API_URL/orders?id=missing-order"

HTTP 404 と Order not found が返るはずです。これは無効なリクエストの 400 とは異なります。ID は期待された形式ですが、アイテムが存在しません。上部の AWS View を確認し、実際の 201、200、400、404 のレスポンスを確認します。呼び出しの Show logs を展開し、入力と結果を比較してください。この手動の UI 確認は、リモートのレコードと実際の実行に対する、独立した読み取り専用のチェックを補完します。

AWS View の検証済みの 2 件の注文と、保持された参照データ; この例は、失敗したリクエストの後に、有効な 2 件の注文レコードだけが残り、参照データも変更されていない状態を示しています。

DynamoDB tables カードまでスクロールし、自分の環境のフィールドを比較してください。

これらの 400 レスポンスはアプリケーションの検証を試します。リクエストは Lambda に到達し、関数が書き込み前に入力を拒否します。後で学ぶ JWT の拒否は、Lambda に到達する前に API Gateway でリクエストを止めます。実行ログと保存された項目を比較して、どの境界で拒否されたかを確認してください。

API と所有する注文リソースを削除する

このステップでは、無関係な参照テーブル、ログ、準備済みの実行ロールを保持しながら、API のリソースを削除します。

リソース一覧は、api-id.txt の API ID、labex-a02-orders-api、その /aws/lambda/labex-a02-orders-api ロググループ、labex-a02-orders、ロールの OrdersData インラインポリシーです。最初に、ルート、統合、default ステージを含めて API を削除します。

aws apigatewayv2 delete-api --api-id "$API_ID"

関数とその呼び出しポリシーを削除します。

aws lambda delete-function --function-name labex-a02-orders-api

別のリソースである関数ロググループを削除します。

aws logs delete-log-group --log-group-name /aws/lambda/labex-a02-orders-api

所有する注文テーブルと、そのデータを削除します。

aws dynamodb delete-table \
  --table-name labex-a02-orders \
  --query TableDescription.TableName \
  --output text

準備済みのロールに追加したデータポリシーだけを削除します。

aws iam delete-role-policy --role-name labex-a02-execution --policy-name OrdersData

削除を証明するため、正常に一覧を読み取ります。認証やネットワークのエラーは、削除の証拠にはなりません。

aws apigatewayv2 get-apis --query 'Items[].Name'
aws lambda list-functions --query 'Functions[].FunctionName'
aws dynamodb list-tables --query TableNames

最初の 2 つのリストは空で、テーブル一覧には labex-a02-reference だけが含まれるはずです。元のアイテムが残っていることを確認します。

aws dynamodb get-item \
  --table-name labex-a02-reference \
  --key '{"id":{"S":"reference"}}' \
  --query Item

message: {"S":"keep unchanged"} が返るはずです。ロールには、無関係なログ出力ポリシーが残っているはずです。

aws iam list-role-policies --role-name labex-a02-execution --query PolicyNames

FunctionLogs だけが表示されるはずです。AWS View では、API、Function、呼び出し記録がなくなり、参照テーブルと INFO platform reference keep unchanged が残っているはずです。これらの準備済みリソースは保持してください。テスト用認証情報と VM の終了は、リソース確認の後に扱われます。

まとめ

正確なデータベースポリシーと API の呼び出し許可を使い、提供された Lambda アプリケーションに 3 つの HTTP ルートを接続しました。異なる計算結果の注文を作成して取得し、実際の DynamoDB レコードを確認して、成功、無効、注文が存在しない場合のレスポンスを区別しました。その後、参照リソースを保持しながら、API とその所有するデータ、関数、ログ、ポリシーを削除しました。

詳しくは、AWS 公式ドキュメントを参照してください:HTTP API の Lambda イベントとレスポンス形式; HTTP API のルートとクエリパラメータ。