Создание и проверка API заказов

AWSBeginner
Практиковаться сейчас

Введение

Команде заказов нужно принимать заказы JSON и возвращать рассчитанные суммы. Вы подключите маршруты состояния, записи и чтения к предоставленному приложению Lambda, проверите допустимые и отклонённые запросы и изучите сохранённые записи DynamoDB перед очисткой.

Сначала выполните Предоставление доступа к функции Lambda через HTTP API и Чтение и запись DynamoDB из Lambda. Эта свежая среда предоставляет приложение, роль и отдельные таблицы.

Связь с сертификацией

Сертификация Задача экзамена Практика
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-маршруты проверки состояния и заказов используют общий backend Lambda; приложение проверяет JSON и сохраняет или читает корректные заказы в DynamoDB.

Подготовьте серверную часть заказов

На этом шаге вы изучите предоставленный контракт запросов, дадите функции доступ к её таблице заказов и развернёте серверную часть. Приложение вычисляет целое число центов: quantity × 250 + сбор за обработку в 100 центов. Используйте AWS View вверху для сравнения запросов с сохранёнными данными.

Перейдите в рабочую папку и прочитайте приложение:

cd /home/labex/project
cat app.py

handler получает формат полезной нагрузки HTTP 2.0. POST /orders ожидает ровно два поля 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"

Роль выполнения управляет исходящим доступом к базе данных. Создайте файл политики с помощью here-document. Маркер 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"

Разверните функцию с десятисекундным тайм-аутом. Приложение читает 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 и подтвердите, что таблица заказов всё ещё содержит ноль элементов. Развёртывание кода и предоставление доступа к данным не создают заказ.

Сопоставьте маршруты состояния и заказов

На этом шаге вы настроите одну интеграцию для трёх маршрутов и дадите каждому маршруту разрешение вызова функции.

Создайте HTTP API и сохраните его идентификатор:

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 серверной части и создайте интеграцию с payload 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 вызывать функцию. Прочитайте идентификатор аккаунта, затем добавьте три точных разрешения вызова. Каждая инструкция использует идентификатор API, буквальное $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

Сформируйте подготовленный адрес 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 подтвердите, что все три ключа маршрутов имеют NONE, у стадии включено AutoDeploy, а вызов состояния вернул 200. Таблица заказов всё ещё должна быть пустой, поскольку этот запрос проверяет состояние, а не сохраняет заказ.

Три маршрута используют один обработчик; запись и чтение заказов используют таблицу заказов, а проверка состояния — нет.

Проверьте допустимые заказы и отклонённые запросы

На этом шаге вы создадите два заказа, прочитаете их сохранённые результаты и проверите сбои до того, как они смогут повлиять на бизнес-данные.

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, quantity 2 и total_cents: 600 (2 × 250 + 100). Статус сообщает клиенту, что заказ сохранён; тело сообщает вычисленный результат.

Создайте второй заказ с другим количеством:

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 сравните две фактические записи таблицы с ответами вызовов 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'

Последние два запроса также возвращают HTTP 400. Их фактические события обработчика могут появиться в CloudWatch Logs, но таблица по-прежнему должна содержать только два допустимых заказа. Проверка запросов происходит в предоставленном приложении перед 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 вызова для сравнения входных данных с результатом. Это ручное изучение интерфейса дополняет независимые проверки удалённых записей и фактических выполнений только для чтения.

Два проверенных заказа и сохранённые эталонные данные в AWS View; Этот пример показывает только две допустимые записи заказов после неудачных запросов рядом с неизменными эталонными данными.

Прокрутите вниз до карточки таблиц DynamoDB, чтобы сравнить эти поля в своей среде.

Эти ответы 400 проверяют валидацию приложения: запрос достигает Lambda, которая отклоняет ввод до записи. Изучаемый далее отказ JWT останавливает запрос в API Gateway до Lambda. Сопоставьте журнал выполнения и сохранённые элементы, чтобы определить границу отказа.

Удалите API и ресурсы заказов работы

На этом шаге вы удалите ресурсы API, сохранив независимую эталонную таблицу, журналы и подготовленную роль выполнения.

Ваш список ресурсов состоит из идентификатора API в api-id.txt, labex-a02-orders-api, его группы журналов /aws/lambda/labex-a02-orders-api, labex-a02-orders и встроенной политики роли OrdersData. Сначала удалите 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

Первые два списка должны быть пустыми; список таблиц должен содержать только 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 обрабатываются после проверки ресурсов.

Резюме

Вы подключили три HTTP-маршрута к предоставленному приложению Lambda с точной политикой базы данных и разрешениями вызова API. Вы создали и получили заказы с разными вычисленными суммами, проверили фактические записи DynamoDB и различили успешные ответы, неверные запросы и отсутствие заказа. Затем вы удалили API и его данные, функцию, журналы и политику работы, сохранив эталонные ресурсы.

Подробнее см. в документации AWS: События Lambda HTTP API и форматы ответов; Маршруты HTTP API и параметры запроса.