【LocalStack実践編】ローカル環境でJavaアプリからLocalStack上のS3にファイルアップロードしてみた
はじめに
AWSを利用するアプリケーションをローカルで開発・テストする際、実環境の準備に手間を感じることはありませんか?
LocalStackを使えば、主要なAWSサービスをローカルマシン上で手軽に再現し、この課題を解決できます。
LocalStackの概要やセットアップ方法については以下の記事で解説しています。
【初心者向け】 LocalStackの概要と基本的な使い方について解説します
本記事ではより実践的な内容として、Windows上のWSL環境を用い、JavaアプリケーションからLocalStack上のS3へファイルをアップロードする具体的な手順をご紹介します。
※なお、本記事ではDockerやJDKのインストール等、環境準備に関する手順については割愛します。
1. LocalStackのセットアップ
以下の様に最低限の設定を記載した「docker-compose.yml」を作成します。
version: '3.8'
services:
localstack:
image: localstack/localstack:latest
container_name: localstack_demo
ports:
- "127.0.0.1:4566:4566"
environment:
# 起動するサービスをS3に限定
- SERVICES=s3
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
「docker-compose.yml」作成後、コンテナを起動します。
docker compose up -d
以下のコマンドでヘルスチェックエンドポイントへアクセスし、サービスのステータス(例: "s3": "running")が返却されれば正常に起動しています。
curl http://localhost:4566/_localstack/health
2. S3バケットの作成
LocalStackのエンドポイントを指定して、S3バケット作成コマンドを実行します。
aws --endpoint-url=http://localhost:4566 s3 mb s3://localstack-demo-bucket
無事、S3バケットが作成されました。
3. Javaコードの実装
VS Code上でMavenプロジェクトを作成し、S3へのファイルアップロードクラスを実装します。
実際には単一のクラスになることは無いと思いますが、今回はより見やすくするために全ての処理を単一のクラスにまとめました。
※ 実際のAWS環境上へのアプリケーションを実装する際には、LocalStack環境向けのエンドポイントや認証情報の設定を変更する必要があります。
/**
* LocalStack S3 バケットにサンプルファイルをアップロードするシンプルなクラス。
* 動作確認を単一クラスで行うことを目的としています。
*/
public class S3FileUploader {
// --- 設定定数 ---
private static final String BUCKET_NAME = "localstack-demo-bucket"; // ★ 事前にAWS CLI等で作成しておくバケット名
private static final String LOCALSTACK_ENDPOINT = "http://localhost:4566";
private static final Region AWS_REGION = Region.AP_EAST_1;
private static final String DUMMY_ACCESS_KEY_ID = "test"; // LocalStack用ダミー認証情報
private static final String DUMMY_SECRET_ACCESS_KEY = "test"; // LocalStack用ダミー認証情報
/**
* S3クライアントを初期化し、ファイルをアップロードします。
*/
public static void main(String[] args) {
System.out.println("Starting S3 file upload process to bucket: " + BUCKET_NAME);
// try-with-resourcesでS3Clientを管理し、自動クローズを保証
try (S3Client s3Client = createLocalStackS3Client()) {
System.out.println("S3Client initialized successfully for endpoint: " + LOCALSTACK_ENDPOINT);
String key = generateTimestampedKey("sample-upload", ".txt");
String content = generateSampleContent();
uploadContent(s3Client, BUCKET_NAME, key, content);
System.out.println("S3 file upload process completed successfully.");
} catch (S3Exception e) {
// S3固有のエラー処理
handleS3Error(e, BUCKET_NAME);
System.exit(1); // エラー終了を示す
} catch (Exception e) {
// その他の予期せぬエラー処理
handleUnexpectedError(e);
System.exit(1); // エラー終了を示す
}
}
/**
* LocalStack接続用に設定されたS3Clientインスタンスを生成します。
*
* @return S3Clientインスタンス
*/
private static S3Client createLocalStackS3Client() {
final AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
AwsBasicCredentials.create(DUMMY_ACCESS_KEY_ID, DUMMY_SECRET_ACCESS_KEY)
);
return S3Client.builder()
.endpointOverride(URI.create(LOCALSTACK_ENDPOINT))
.credentialsProvider(credentialsProvider)
.region(AWS_REGION)
.build();
}
/**
* 現在時刻を含むユニークなS3オブジェクトキーを生成します。
*
* @param prefix プレフィックス(例: "uploads/")
* @param suffix サフィックス(例: ".txt")
* @return 生成されたキー文字列
*/
private static String generateTimestampedKey(String prefix, String suffix) {
return String.format("%s-%d%s", prefix, Instant.now().toEpochMilli(), suffix);
}
/**
* アップロードするサンプルコンテンツを生成します。
*
* @return サンプルコンテンツ文字列
*/
private static String generateSampleContent() {
return "This is a sample content uploaded by S3FileUploader at " + Instant.now();
}
/**
* 指定された文字列コンテンツをS3にアップロードします。
*
* @param s3Client 使用するS3Client
* @param bucketName ターゲットバケット名
* @param key S3オブジェクトキー
* @param content アップロードする文字列コンテンツ
* @throws S3Exception S3操作中にエラーが発生した場合
*/
private static void uploadContent(S3Client s3Client, String bucketName, String key, String content) throws S3Exception {
System.out.printf("Attempting to upload content to s3://%s/%s%n", bucketName, key);
PutObjectRequest putRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(key)
.contentType("text/plain") // コンテンツタイプを明示的に指定
.build();
s3Client.putObject(putRequest, RequestBody.fromString(content));
System.out.println("Successfully uploaded content.");
}
/**
* S3Exception発生時のエラー情報を標準エラー出力に表示します。
*
* @param e キャッチしたS3Exception
* @param bucketName 操作対象だったバケット名
*/
private static void handleS3Error(S3Exception e, String bucketName) {
System.err.println("---- S3 Error Details ----");
System.err.println("Error Message: " + e.awsErrorDetails().errorMessage());
System.err.println("Error Code: " + e.awsErrorDetails().errorCode());
System.err.println("AWS Request ID: " + e.requestId());
System.err.println("Target Bucket: " + bucketName);
if ("NoSuchBucket".equals(e.awsErrorDetails().errorCode())) {
System.err.println("Hint: The specified bucket does not exist. Please create it first.");
}
System.err.println("--------------------------");
e.printStackTrace(System.err); // スタックトレースも表示
}
/**
* 予期せぬ例外発生時のエラー情報を標準エラー出力に表示します。
* @param e キャッチしたException
*/
private static void handleUnexpectedError(Exception e) {
System.err.println("---- Unexpected Error ----");
System.err.println("Error Message: " + e.getMessage());
System.err.println("--------------------------");
e.printStackTrace(System.err);
}
}
4. 動作確認
実装したJavaコードを実行し、LocalStack S3へのファイルアップロードを確認します。
VS CodeでS3FileUploader.java を開き、mainメソッド上部の「Run」をクリックします。
エラーが発生せず、実行ログに "S3 file upload process completed successfully." と表示されれば正常に処理が完了しています。
以下のコマンドを実行し、ファイルがアップロードされているか確認します。
aws --endpoint-url=http://localhost:4566 s3 ls s3://localstack-demo-bucket
無事、アップロードされていることが確認できました。
さいごに
本記事では、LocalStackを用いてローカル環境にAWS S3のエミュレーション環境を構築し、Javaアプリケーションからファイルをアップロードする手順を説明しました。
LocalStackの利用により、実際のAWS環境を用意することなく、ローカルでの開発・テストを効率的かつ低コストで実施できることが確認できたかと思います。
今回ご紹介したS3以外にも多くのAWSサービスに対応しており、様々な開発シナリオで応用可能です。
サポートされているAWSサービスの詳細については以下の公式ドキュメントから確認できます。
LocalStack公式サイト|サポート範囲
この記事が、皆様の開発ワークフロー改善のヒントとなれば幸いです。