0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWSハンズオン_API開発_JavaSDK

0
Last updated at Posted at 2026-04-05

API開発(Java SDK)ハンズオン記録

1. はじめに

この記事はAIエージェントを利用して生成しています。

このハンズオンの目的は、Lambda(Java)で最小構成の受付APIを構築し、設計・IaC・テスト・運用まで一連で整えることです。
ハンズオンでは、次の構成にしました。

  • API Gateway(REST API)
  • Lambda(Java 21)
  • DynamoDB(受付状態管理)
  • S3(アップロード先、Presigned URL)

AWS公式ハンズオンでは、上記を満たすハンズオンが存在しなかったため、以下公式サンプルの強みを組み合わせました。

  • 骨格: aws-samples/aws-sam-java-rest
  • Presigned URL: aws-samples/generate-s3-accelerate-presigned-url
  • 拡張観点の補助: aws-samples/bobs-used-bookstore-serverless

2. 全体像の検討

ハンズオン構築にあたり
最初に「誰が誰と通信するか」を検討しました。

  • 利用者が API Gateway の POST /requests を呼ぶ
  • Lambda が requestId を発行し、DynamoDB に受付情報を保存
  • Lambda が S3 Presigned URL を返す
  • 利用者が S3 に直接アップロード
  • GET /requests/{id}GET /requests で状態参照

全体構成図

スクリーンショット 2026-04-05 15.03.23.png

3. 工程1: APIの最小構成を決める

初期版のAPIは3本に限定しました。

  • POST /requests
  • GET /requests/{id}
  • GET /requests

工夫:

  • 受付作成、単票取得、一覧取得だけに絞り、説明性を優先
  • 認証・通知・高度な非同期は初期スコープから外して複雑化を回避
  • 入口は RequestApiHandler に集約し、外部I/Oの窓口を明確化

4. 工程2: Lambda内部の責務を整理する

同期系と非同期系を分けて、責務を整理しました。

  • 同期系(RequestApiFunction): RequestApiHandler(ルーティング、DTO変換、例外レスポンス化)
  • 同期系(RequestApiFunction): RequestService(業務処理、ID生成、状態決定、URL発行連携)
  • 同期系(RequestApiFunction): RequestRepository(DynamoDBアクセス)
  • 同期系(RequestApiFunction): RequestS3PresignService(Presigned URL生成)
  • 同期系(RequestApiFunction): RequestValidator(入力妥当性、body/path)
  • 非同期系(RequestStatusUpdateFunction): RequestStatusUpdateHandler(S3 Object CreatedイベントからrequestIdを抽出)
  • 非同期系(RequestStatusUpdateFunction): RequestRepositoryrequestIdをキーにstatus=COMPLETEDへ更新)

工夫:

  • Handlerを薄く保ち、入力検証は RequestValidator に委譲
  • Util を安易な退避先にせず、責務が読める部品名を採用
  • 1関数構成でも保守可能な内部設計を優先

5. 工程3: SAMテンプレートに落とし込む

目的:
GUIで理解した構成を、再現可能なIaCへ落とし込むことにしました。
画面で全体像を把握と設定値の確認、その後にtemplate.yamlへ戻して運用可能な形へ変換することにしました。

この節で確認すること:

  • sam deploy --guided の入力値を再利用できること
  • GUIで作った構成がSAMで再現できること

template.yamlの責務(何を定義するか):

  • API Gateway(REST, Prod
  • Lambda(Java 21, RequestApiHandler
  • S3 Object Created を受ける EventBridge ルール + RequestStatusUpdateHandler
  • 既存 DynamoDB / 既存 S3 の参照
  • API 3ルートのLambda統合
  • Lambda環境変数

template.yamlenv.local.json の関係:

  • template.yaml: AWSにデプロイする本体構成を定義する
  • env.local.json: sam local 実行時だけ、接続先をLocalStackへ差し替える

重要前提:

  • template.yaml では可変値(例: DDB_ENDPOINT, S3_ENDPOINT)を空文字で持つ
  • Java側 optional(...)isBlank()null へ変換し、未指定扱いにする

設計判断(S3イベント連携):

  • 最小要件だけなら S3 通知の Lambda 直結でも実現可能
  • 本ハンズオンでは、将来の連携先追加や分岐に対応しやすい実務想定を重視して EventBridge 経由を採用
  • SAM上で EventBridge ルールを定義し、イベント連携の責務を構成として固定した

APIの通信テスト

curl -i -X POST "<INVOKE_URL>/requests" \
  -H "Content-Type: application/json" \
  -d '{"userId":"u001","fileName":"a.txt"}'

API通信テストのレスポンス

{"requestId":"req-63371ecc-34d2-4157-bab7-4f3b03d657d2","userId":"u001","fileName":"a.txt","s3Key":"uploads/u001/req-63371ecc-34d2-4157-bab7-4f3b03d657d2/a.txt","status":"RECEIVED","uploadUrl":"https://handson-fileups3-","createdAt":"YYYY-mm-ddTHH:MM:SS.小数点以下","updatedAt":"YYYY-mm-ddTHH:MM:SS.小数点以下"}% 

6-1. AWS実環境へのSAMデプロイ手順

目的:
SAMでAWS実環境に反映し、ApiBaseUrl でAPI疎通まで確認すること。

この節で確認すること:

  • sam deploy --guided の設定が保存されること
  • 2回目以降は sam deploy だけで更新できること
  • デプロイ後にAPI 3ルートが利用できること

手順/設定:

  1. AWS CLIログイン
# IAMユーザー利用時
aws configure

# SSO利用時
aws configure sso
aws sso login --profile <profile-name>

参考:

AWS CLIログイン手順(参考)

私の過去の記事です。
わかりにくいですが、AWS CLIログインする作業の流れ自体はまとまっていると思います。

  1. 認証確認
aws sts get-caller-identity
  1. テンプレート検証
sam validate
  1. ビルド
sam build
  1. 初回デプロイ(対話形式)
sam deploy --guided

--guided の入力は次を使用します(環境固有値は作成済みリソース名を指定)。

  • 固定値:
    • Stack Name: fileupapi-aws
    • AWS Region: ap-northeast-1
    • Confirm changes before deploy: n
    • Allow SAM CLI IAM role creation: y
    • Disable rollback: n
    • ...has no authentication...: y
    • Save arguments to configuration file: y
  • 環境依存値:
    • UploadBucketName: 作成済みS3バケット名
    • RequestsTableName: 作成済みDynamoDBテーブル名

このハンズオンではap-northeast-1リージョンにしていますが、おそらく基本的にどこでも大丈夫です。
ただし、全体で統一するように注意してください。
sam deploy では、Lambdaアーティファクトを一時保管するための管理用S3バケットが自動生成される。
このバケットは、アプリ本体のアップロード先S3(UploadBucketName)とは用途が異なる。

補足. 著者の失敗例: 管理用S3を削除してしまった場合

著者は一度、SAM管理用S3バケットを不要と誤認して削除し、sam deploy で次のエラーになりました。

  • Error: Unable to upload artifact ... S3 Bucket does not exist.

この場合は、まず管理スタックの状態をAWS CLIで確認します。

aws cloudformation describe-stacks --stack-name aws-sam-cli-managed-default --region ap-northeast-1

管理スタックが壊れた状態で残っている場合は、管理スタックを削除して再作成させます。

aws cloudformation delete-stack --stack-name aws-sam-cli-managed-default --region ap-northeast-1

その後に再度デプロイを実行します。

sam deploy --guided

この手順で、SAM管理用S3バケットを含む管理リソースが再生成され、通常の sam deploy に復帰できます。

  1. 2回目以降のデプロイ
sam deploy

samconfig.toml に保存された設定を利用するため、--guided は不要です。

  1. SAMデプロイ完了時の出力例:
CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 ApiBaseUrl
Description         Base URL for the deployed API
Value               https://自動生成箇所.XXXXXXXX.ap-northeast-1.amazonaws.com/Prod

Key                 RequestsTableName
Description         DynamoDB table name
Value               requests

Key                 UploadBucketName
Description         S3 bucket name
Value               作成したS3バケット名(既存)
-------------------------------------------------------------------------------------------------------------------------------------------------------------


Successfully created/updated stack - fileupapi-aws in ap-northeast-1
  1. デプロイ後の疎通確認
curl -i -X POST "<INVOKE_URL>/requests" \
  -H "Content-Type: application/json" \
  -d '{"userId":"u001","fileName":"a.txt"}'

curl -i "<INVOKE_URL>/requests"
curl -i "<INVOKE_URL>/requests/<requestId>"
結果
  • sam deploy --guidedApiBaseUrl を払い出し
  • 払い出しURLで POST /requestsGET /requests/{id}GET /requests の再検証に成功
  • GUI構築時の動作を、SAM経由でも再現できることを確認
S3バケットへのPUT結果

スクリーンショット 2026-04-05 10.34.01.png

DBのステータス更新

スクリーンショット 2026-04-05 10.34.59.png

6. 工程4: テストを段階分けして設計する

目的:
CIでの「速い検知」と手起動での「実環境での確実性」を両立することで、テストとしての運用性と品質担保を目的。

この節で確認すること:

  • なぜ単体/LocalStack/AWS実環境の3層に分けるか
  • PR必須CI(verify)で何を保証するか
  • AWSでしか確定できない項目をどこで確認するか

全体方針(3層構成):

  • 単体/契約テスト: API通信の動作および契約に関して、コード変更で壊れていないかを高速に検知する
  • SAM + LocalStack: API入口からDynamoDB/S3連携までのローカル疎通を確認する
  • AWS実環境確認: IAM・実エンドポイント・イベント連携を最終確認する

分離した理由は、全てをPR必須CIに載せると実行時間と不安定要素が増え、日常開発のフィードバックが遅くなるためです。
そのため、CIは軽量で再現性の高い検証に絞り、重い確認は段階を分けて実施しています。

6-1. テスト戦略(全体)

  • PR必須CI: mvn verify(高速・安定を優先)
    • GitHub Actionsで pull_requestmaster向け)と pushmaster)をトリガーに実行
  • AWS依存の重い確認: 手動実施(マージ前/リリース前)
  • SAM + LocalStack: 補助的なローカル統合検証

6-2. 単体/契約テスト

  • RequestValidatorTest
  • RequestS3KeyBuilderTest
  • RequestServiceTest
  • RequestApiHandlerTest

確認観点:

  • 入力検証(必須・形式)
  • DTOマッピング
  • ハンドラー契約(200/400/404/500

6-3. SAM + LocalStack 統合検証

目的:

  • API Gateway経由のLambda呼び出し(sam local start-api
  • LambdaからDynamoDB/S3への接続
  • Presigned URLを使ったClient -> S3 PUT
  • GET /requests/{id}で状態確認(ローカルで確認可能な範囲)

注意点(ローカルの限界):

  • 分かる: API契約、入力検証、DynamoDB保存取得、Presigned URL発行とPUT疎通
  • 分からない: AWS本番のEventBridge連携最終挙動、IAM実権限差分

必要なもの(前提):

  • Java 21
  • Maven
  • Docker Desktop(起動済み)
  • SAM CLI
  • LocalStack(s3,dynamodb,events
  • env.local.jsonDDB_ENDPOINT / S3_ENDPOINT / AWS_REGION

env.local.jsonを用意する理由:

  • sam localで起動したLambdaに、ローカル接続先を環境変数で明示注入するため
  • 本番向けコードを変更せずに、接続先だけをLocalStack向けへ切り替えるため
  • 毎回の手動exportを減らし、再実行時の設定ブレを防ぐため

env.local.json例:

{
  "RequestApiFunction": {
    "DDB_ENDPOINT": "http://host.docker.internal:4566",
    "S3_ENDPOINT": "http://s3.localhost.localstack.cloud:4566",
    "AWS_REGION": "ap-northeast-1",
    "AWS_DEFAULT_REGION": "ap-northeast-1",
    "REQUESTS_TABLE_NAME": "requests",
    "UPLOAD_BUCKET_NAME": "fileupapi-local-uploads"
  },
  "RequestStatusUpdateFunction": {
    "DDB_ENDPOINT": "http://host.docker.internal:4566",
    "AWS_REGION": "ap-northeast-1",
    "AWS_DEFAULT_REGION": "ap-northeast-1",
    "REQUESTS_TABLE_NAME": "requests"
  }
}

env.local.json利用手順:

  1. env.local.jsonをプロジェクトルートに配置する
  2. sam local start-api --env-vars env.local.jsonで起動する
  3. POST /requests実行後、返却のuploadUrlPUTする
  4. GET /requests/{id}GET /requestsで状態を確認する

手順/設定(最小手順):

  • LocalStack起動
  • S3バケット作成 + 直後存在確認
  • DynamoDBテーブル作成 + 直後存在確認
  • sam build
  • sam local start-api --env-vars env.local.json
  • POST /requestsuploadUrl取得
  • uploadUrlPUT
  • GET /requests/{id}GET /requestsで確認

再現時の注意点(ハマりどころ):

  • S3エンドポイントはLocalStack公式に沿う(s3.localhost.localstack.cloud
  • 署名付きURLは毎回再発行(期限・再利用回避)
  • uploadUrlは手打ちせずレスポンスからそのまま利用
  • LocalはEventBridge連携の最終確認環境ではない(最終確認はAWS実環境)

結果:

  • 契約確認: 404/400/400
  • 正常系: POST /requests -> 201, GET /requests/{id} -> 200, GET /requests -> 200
  • API契約とDynamoDB連携のローカル再現を確認
  • Presigned URL生成の統合確認

6-4. AWS実環境確認

目的:
ローカルでは確定できない項目(IAM、EventBridge、実S3)を最終確認すること。

この節で確認すること:

  • EventBridgeが実際に起動し、状態更新まで到達すること
  • IAM権限が実環境で不足しないこと
  • 実S3のPUT後にGET /requests/{id}COMPLETEDへ遷移すること

確認シナリオの意味:

  • POST /requests は受付を作成し、初期状態 RECEIVED を確定する
  • uploadUrl への PUT 後、イベント駆動でステータス更新Lambda(RequestStatusUpdateFunction)が起動する
  • GET /requests/$REQUEST_IDCOMPLETED を確認し、POST -> PUT -> COMPLETED の因果を確認する

イベント駆動の実行経路:

  • 利用者が uploadUrlPUT 実行
  • S3 Object Created を EventBridge が受信
  • EventBridge がステータス更新Lambda(RequestStatusUpdateFunction)を起動
  • RequestStatusUpdateFunction が DynamoDB の対象レコードを COMPLETED に更新

手順/設定(時系列):

  • POST /requestsrequestIduploadUrl を取得
  • uploadUrlPUT
  • GET /requests/$REQUEST_IDstatus=COMPLETED を確認
  • GET /requests で一覧確認

実行コマンド:

RESPONSE_JSON=$(curl -s -X POST "<INVOKE_URL>/requests" \
  -H "Content-Type: application/json" \
  -d '{"userId":"u001","fileName":"a.txt"}')

echo "$RESPONSE_JSON"

REQUEST_ID=$(echo "$RESPONSE_JSON" | jq -r '.requestId')
UPLOAD_URL=$(echo "$RESPONSE_JSON" | jq -r '.uploadUrl')

curl -i -X PUT "$UPLOAD_URL" \
  --upload-file /path/to/upload-file.txt

curl -i "<INVOKE_URL>/requests/$REQUEST_ID"
curl -i "<INVOKE_URL>/requests"

結果:

  • POST /requestsGET /requests/{id}GET /requests のスモークが成功
  • DynamoDB requests への保存を確認
  • S3 PUT後の status=COMPLETED 反映を確認

失敗時対応(再発防止):

  • EventBridgeが未起動の場合、まず対象S3バケットの Amazon EventBridge 連携設定を確認する
  • ルールの source/detail-type/bucket.name を実バケット名と突合する
  • ステータス更新Lambda(RequestStatusUpdateFunction)のCloudWatch Logsで、requestId 抽出失敗やDynamoDB更新エラーを確認する
  • 署名付きURLは毎回再発行し、レスポンスの uploadUrl をそのまま使う

6-5. RepositoryテストをCIに置かない理由

RequestRepository はDynamoDBとの境界層です。
この層はSDKモックだけでは実契約を十分に再現しにくいため、PR必須の verify には含めていません。

代替として、次のレイヤで確認しています。

  • SAM + LocalStack で疎通確認(保存/取得/一覧)
  • 実AWSスモークで最終確認(IAM・実エンドポイント・実データ反映)

方針としては「Repositoryを未検証にする」のではなく、
CIは軽量契約に限定し、永続化契約は統合レイヤで分離確認する運用です。

7. 工程5: GitHub運用とCIを固める

開発ルールは次で固定しました。

  • 機能追加やコード改修の場合は、新規ブランチを切ってコミット
  • Masterブランチへの直接プッシュ禁止
     - PR必須(required status check は verify
     - strict(Require branches to be up to date before merging)有効
     -

工夫:

  • ジョブを増やしすぎず、壊れたら困る箇所のみCIで常にテスト
     - API通信の処理や全体のビルド

スクリーンショット 2026-04-05 15.26.39.png
一人で開発しているので、ひたすらコミット、プルリク、マージを繰り返しています。

8. 実装・検証で得た学び

8-1. 非同期完了判定の設計判断

Presigned URL方式では、アップロード通信の終端は Client -> S3 です。
そのため、利用者向けの「アップロード完了」は、S3へのPUT成功(HTTP 2xx)を基準に判定します。

一方で、status=COMPLETED 反映は S3 -> EventBridge -> Lambda -> DynamoDB の非同期処理です。
この反映完了を同一レスポンスで待たせる設計は採用せず、バックエンド整合として追従させることにしました。

採用判断:

  • 前提AWSハンズオンとしてそこまで厳密な即時通知を求めていない
  • クライアント側の完了通知は、S3 PUT成功で即時表示する
  • DynamoDB反映は非同期で処理し、必要時のみ GET /requests/{id} で参照する

8-2. 検証で得た失敗知見と是正

env.local.jsonに記載するS3_ENDPOINTですが、
当初は Docker の一般的な接続先として host.docker.internalS3_ENDPOINT に設定して検証しました。
しかし、以下のエラーが発生しました。

  • curl: (6) Could not resolve host: fileupapi-local-uploads.host.docker.internal
  • NoSuchBucketBucketName=uploads

原因は、Presigned URL の解釈が S3 の Host/Path ルールに依存する点を、LocalStack仕様と照合せずに設定したことです。
LocalStack 公式リファレンスを確認すると、S3では Path-style / Virtual-hosted-style の扱いが明示されており、Virtual-hosted方式でbucketを正しく解釈するには s3. プレフィックスのあるエンドポイント(例: s3.localhost.localstack.cloud)を使うべきと記載されていました。

参照:

9. まとめ

今回のAWSハンズオン構築で以下の点を学ぶことができました。

  • API 3本の実装とAWS実環境スモーク
  • Lambda内部の責務分離
  • SAMへの再現可能な反映
  • CIと実環境確認の役割分離

Javaを用いてのAPI開発とJava SDKの利用、AWS SAMでの環境再現などをざっくりと学ぶことができたので、トライしてよかったです。

有効だった工夫:

  • 方針を先に固定し、工程ごとに判断理由を明示したこと
  • 先にGUIで理解し、後でSAMに戻して再現性を確保したこと
  • テストを段階分けし、CIの範囲と手作業での範囲を分けたこと。

今後の拡張:

  • SAM + LocalStack の自動化(スクリプトを利用した自動実行)
  • 認証・監視を含む本番寄り運用への拡張

以上、ここまでお付き合いいただきありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?