こんにちは、ソーイの髙﨑です。
業務でLaravelとDynamoDBを連携する機会があり、
ローカル環境の構築からCRUD実装までを一通り行いました。
本記事では、その際に行った設定手順や実装方法についてまとめます。
目次
1.はじめに
2.DynamoDBとは
3.全体アーキテクチャ構成
4.事前準備
5.CRUD操作の実装例
6.DynamoDB特有のハマりどころ
7.まとめ
はじめに
この記事で分かること
・LaravelからDynamoDBへ接続する方法
・DynamoDB Local環境の構築手順
・AWS SDK for PHPを用いたCRUD実装例
・DynamoDB特有の設計上の注意点
本記事では、「DynamoDBとは何か」というところからAWS SDK for PHP(DynamoDbClient)を用いたLaravel環境との連携方法の手順についてまとめています。
本記事を読むことで、LaravelからDynamoDBへ接続し、基本的なCRUD処理を実装できるようになります。
本記事は下記環境での実装を想定しています。
php:8.3
laravel:11
aws-sdk-php:3.1
DynamoDBとは何か
DynamoDBはAWSが提供するフルマネージドなNoSQLデータベースです。 RDB(MySQLなど)とは異なり、
- スキーマレス
- JOINが存在しない
- アクセスパターンを先に設計する
といった特徴があります。
パーティションキーとは
DynamoDBでは、データを一意に識別するために Partition Key(主キー)を設定します。 今回の例では id がそれに該当します。
Global Secondary Index(GSI)とは
主キー以外の属性で検索を可能にする仕組みです。 RDBの「インデックス」に近い概念ですが、 DynamoDBでは後付けではなく 設計段階で考慮する必要があります。
全体アーキテクチャ構成
今回はDynamoDBと連携し、基本的なCRUD操作を実装します。
アーキテクチャ構造は下記になります。

Laravelアプリケーションから、サービスクラス(DynamoDbService)を通じて AWS SDK for PHP を利用し、DynamoDBへアクセスします。
HTTPリクエストはControllerで受け取り、内部でDynamoDbServiceを呼び出すことで、
PutItem / GetItem / UpdateItem / DeleteItem といった操作を実行します。
開発環境では Docker 上の DynamoDB Local に接続し、
本番環境では AWS 上の DynamoDB に接続する構成になります。
事前準備(DynamoDB localコンテナの生成・ライブラリの導入)
本記事では、ローカル環境で動作確認を行うために、以下の3つのコンテナを利用します。
-
dynamodb-local
→ ローカルで動作するDynamoDB本体 -
dynamodb-setup
→ 起動時にテーブルを自動作成する初期化用コンテナ -
dynamodb-admin
→ DynamoDBをGUIで確認できる管理画面
DynamoDB Localを利用することで、AWS上のDynamoDBへ接続せずに開発・テストを行うことができます。
AWS SDK for PHP のインストール
LaravelからDynamoDBへ接続するために、AWS SDK for PHPをインストールします。
composer require aws/aws-sdk-php
docker-composeの設定
docker-compose.yml の services に以下を追加します。
dynamodb-local:
image: amazon/dynamodb-local:latest
ports:
- "8080:8000"
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath /home/dynamodblocal/data"
volumes:
- "./docker/dynamodb/data:/home/dynamodblocal/data"
working_dir: /home/dynamodblocal
environment:
- AWS_ACCESS_KEY_ID=dummy
- AWS_SECRET_ACCESS_KEY=dummy
- AWS_REGION=ap-northeast-1
dynamodb-setup:
image: amazon/aws-cli:latest
depends_on:
- dynamodb-local
environment:
- AWS_ACCESS_KEY_ID=dummy
- AWS_SECRET_ACCESS_KEY=dummy
- AWS_DEFAULT_REGION=ap-northeast-1
- APP_ENV=${APP_ENV:-local}
volumes:
- "./docker/dynamodb/init-tables.sh:/init-tables.sh"
entrypoint: >
/bin/sh -c "
chmod +x /init-tables.sh;
/init-tables.sh;
echo 'DynamoDB setup completed';
"
dynamodb-admin:
image: aaronshaf/dynamodb-admin:latest
ports:
- "8081:8001"
environment:
- DYNAMO_ENDPOINT=http://dynamodb-local:8000
- AWS_REGION=ap-northeast-1
- AWS_ACCESS_KEY_ID=dummy
- AWS_SECRET_ACCESS_KEY=dummy
depends_on:
- dynamodb-local
- dynamodb-setup
dynamodb-setup部分にテーブルを生成するinit-tables.shを指定しています。
dynamodb-setupコンテナでは、AWS CLIを使用してsimple-logsテーブルを自動生成しています。
Local環境では --endpoint-url を指定することで DynamoDB Local に接続しています。
※initスクリプトを使用しない場合は、dynamodb-admin 画面から手動でテーブルを作成することも可能です。
init-tables.sh
#!/bin/sh
aws dynamodb create-table \
--table-name simple-logs \
--attribute-definitions \
AttributeName=id,AttributeType=S \
--key-schema \
AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--endpoint-url http://dynamodb-local:8000
.envファイルに下記を追加します。
AWS_ACCESS_KEY_ID=dummy
AWS_SECRET_ACCESS_KEY=dummy
AWS_DEFAULT_REGION=ap-northeast-1
DYNAMODB_ENDPOINT=http://dynamodb-local:8000
DYNAMODB_ENDPOINTを指定することで、
AWS上のDynamoDBではなくローカルのDynamoDBへ接続することができます。
本番環境ではこの値を設定せず、
AWSのエンドポイントへ接続する構成になります。
Dockerコンテナを起動します。
docker compose up -d
http://localhost:8081/にアクセスするとdynamodbの操作画面が表示されます。

以上で準備は完了です。次からは実際にDynamoDBテーブルへのCRUD操作を実装していきます。
CRUD操作の実装例
今回は、簡素なsimple-logsテーブルのデータを操作することを想定したCRUD処理の実装例を紹介します。
下記はsimple-logsテーブルの構成です。
| 属性名 | 型 | 説明 |
|---|---|---|
| id | String | パーティションキー(主キー) |
| action | String | 実行されたアクション内容 |
| created_at | String | 作成日時(ISO8601形式) |
| updated_at | String | 更新日時(ISO8601形式) |
まず、DynamoDbService.phpを作成し、その中に処理を記入していきます。
<?php
namespace App\Services;
use Aws\DynamoDb\DynamoDbClient;
use Carbon\Carbon;
use Aws\Exception\AwsException;
use Illuminate\Support\Facades\Log;
class DynamoDbService
{
protected DynamoDbClient $client;
private string $table;
public function __construct()
{
$this->client = new DynamoDbClient([
'region' => env('AWS_DEFAULT_REGION'),
'version' => 'latest',
'endpoint' => env('DYNAMODB_ENDPOINT', null),
'credentials' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
],
]);
// テーブル名を設定
$this->table = 'simple-logs';
}
// 以下に処理を追加
}
データの追加
データを追加するCreateメソッドを実装します。
DynamoDBでは型を明示する必要があり、S(String)、N(Number)、BOOLなどを指定します。
また、timestamp(created_at/updated_at)の自動追加・更新がないため、自前で実装します。
/**
* Create: ログを1件追加
*/
public function create(string $id, string $action): void
{
$now = Carbon::now()->toIso8601String();
try {
$this->client->putItem([
'TableName' => $this->table,
'Item' => [
'id' => ['S' => $id],
'action' => ['S' => $action],
'created_at' => ['S' => $now],
'updated_at' => ['S' => $now],
],
]);
} catch (AwsException $e) {
Log::error('DynamoDbService::create failed', ['error' => $e->getMessage()]);
throw $e;
}
}
データの取得
idで指定したデータを1件取得するfindメソッドを実装します。
/**
* Read: id で1件取得
*/
public function find(string $id): ?array
{
try {
$result = $this->client->getItem([
'TableName' => $this->table,
'Key' => [
'id' => ['S' => $id],
],
]);
} catch (AwsException $e) {
Log::error('DynamoDbService::find failed', ['error' => $e->getMessage()]);
throw $e;
}
if (!isset($result['Item'])) {
return null;
}
$item = $result['Item'];
return [
'id' => (string) $item['id']['S'],
'action' => (string) $item['action']['S'],
'created_at' => (string) $item['created_at']['S'],
'updated_at' => (string) $item['updated_at']['S'],
];
}
データの更新
データの更新を行うupdateActionメソッドを実装します。
updated_atを一緒に更新しています。
/**
* Update: action を更新(updated_at も更新)
*/
public function updateAction(string $id, string $action): void
{
$now = Carbon::now()->toIso8601String();
try {
$this->client->updateItem([
'TableName' => $this->table,
'Key' => [
'id' => ['S' => $id],
],
'UpdateExpression' => 'SET action = :action, updated_at = :updated_at',
'ExpressionAttributeValues' => [
':action' => ['S' => $action],
':updated_at' => ['S' => $now],
],
]);
} catch (AwsException $e) {
Log::error('DynamoDbService::updateAction failed', ['error' => $e->getMessage()]);
throw $e;
}
}
データの削除
idで指定したデータを削除するdeleteメソッドを実装します。
/**
* Delete: id で1件削除
*/
public function delete(string $id): void
{
try {
$this->client->deleteItem([
'TableName' => $this->table,
'Key' => [
'id' => ['S' => $id],
],
]);
} catch (AwsException $e) {
Log::error('DynamoDbService::delete failed', ['error' => $e->getMessage()]);
throw $e;
}
}
DynamoDB特有のハマりどころ
① timestamp(created_at / updated_at)は自動で更新されない
LaravelのEloquentでは、$timestamps = true を設定することで
created_at / updated_at が自動更新されます。
しかし、DynamoDBにはそのような仕組みはありません。
DynamoDBはORMを持たないスキーマレスなデータベースであり、
自動的に日時を更新する機能は提供されていません。
そのため、アプリケーション側で明示的に日時を設定する必要があります。
また、更新時にも updated_at を自分で更新する必要があります。
② 検索のためにはIndex(GSI)を事前に設定する必要がある
DynamoDBでは、主キー(Partition Key)以外での検索は基本的にできません。
例えば、今回のテーブル構成では id が主キーとなっているため、
id 以外での高速検索はできません。
もし action や created_at で検索したい場合は、
Global Secondary Index(GSI)を事前に作成する必要があります。
Scanを使用すれば検索自体は可能ですが、
Scanはテーブル全件を読み込む処理のため、大量データがある場合は
パフォーマンスやコストの観点から推奨されません。
そのため、DynamoDBでは「どのキーで取得するか」を事前に設計し、
必要に応じてGSIを作成することが重要です。
まとめ
本記事では、Laravel環境からDynamoDBへ接続し、
AWS SDK for PHPを用いて基本的なCRUD処理を実装する方法を紹介しました。
業務では、ビデオチャットに用いるチャンネルデータを高スループットで扱う必要があったため、スケーラビリティに優れたDynamoDBを採用しました。
DynamoDBはRDBとは設計思想が大きく異なり、「テーブル設計」よりも「アクセスパターン設計」を先に考える必要があります。
特に、主キー以外で検索する場合はGSIの設計が重要になります。
実装当初は「あとから検索すればよい」と考えていましたが、DynamoDBではアクセスパターンを事前に設計しなければScanに頼る構成になってしまうと気付きました。
NoSQLに初めて触れる中で、timestampの自力実装やGSIの作成、キー設計など、RDBとは異なる視点で設計する重要性を学ぶことができました。
お知らせ
技術ブログを週1〜2本更新中、ソーイをフォローして最新記事をチェック!
https://qiita.com/organizations/sewii