PHPで学ぶAmazon DynamoDB

# はじめに
[前回](http://qiita.com/snoguchi/items/87f244a02087fe3fceb5)はAmazon DynamoDBの基本概念について書きましたが、今回はPHPのSDKからのDynamoDB操作についてまとめました。
 

**前提条件**
aws.pharがインストール済み

**作業イメージ**

今回のフォルダ構成は以下3ファイルのこんな感じの想定です。

┏dynamo.php
┣aws.phar
┗config.php

config.phpでProfile情報を設定して、dynamo.phpで各種DynamoDB操作を行うイメージです。


# configファイルを作る
ここにAWS SDKを使う為に必要な情報を記載していきます。
今回はconfig.phpとしてファイルを作成しました。

```php:config.php

return array(
    'includes' => array('_aws'),
    'services' => array(
        'default_settings' => array(
            'params' => array(
                'key'    => 'AWSのキー',
                'secret' => 'AWSのシークレットキー',
                'region' => 'リージョンを指定'
            )
        )
    )
);

```

# インポートと初期化処理

```php:dynamo.php
<?php

require_once('aws.phar');

use Aws\Common\Aws;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Exception\DynamoDbException;

//作成済みのconfig.phpを使って$clientの生成
$aws = Aws::factory('config.php');
$client = $aws->get('dynamodb');

```

# getItem

```php:dynamo.php

//ConsistentReadの指定(読込の整合性)
$get_item_array['ConsistentRead'] = true;

//テーブル指定
$get_item_array['TableName'] = 'test';

//キーはidでString型の'test_id'の条件で検索
$get_item_array['Key']['id'] = array('S'=>'test_id');

//生成した$clientを使ってgetitem処理実行
$result = s$client->getItem($get_item_array);

```

### **Tips**

#### ・ConsistentReadについて
`$get_item_array['ConsistentRead'] = true;` 
上記コードでは読み取りの整合性を設定しています。
指定しなければデフォルトのfalse (結果整合性)がセットされています。
この機能はgetItem以外ではQueryでも提供されています。


| 値 | 意味 | メリット | デメリット |
|:----------- |------------:|------------:|------------:
| true        |強い整合性での読み取り|必ず最新の結果を含めて結果を返す|厳密な結果を返すためレスポンスが遅れる場合がある
| false       |結果整合性での読み取り|レスポンスが強い整合性より速い|最新の結果は反映されない場合がある


個人的な所感ですが、通常のサイト内検索、例えばユーザーが投稿したコンテンツの情報の検索結果などは多くの場合厳密な整合性結果は求められないはずなので**結果整合性(false)**で行い、ユーザー登録情報や課金に関する情報など、重複出来なかったり、リアルタイムでの確実な検索が必要な場合は**強い整合性(true)**を使う感じかなと思います。

#### ・データ型について

```
//キーはidでString型の'test_id'の条件で検索
$get_item_array['Key']['id'] = array('S'=>'test_id');
```
上記コードでは検索条件のidに対して「String型ですよ！」と明示しています。
'S'の部分が Stringと明示している箇所です。

このようにDynamoDBのAPIの作法ではデータ操作時にデータ型を明示する必要があります。

[前回](http://qiita.com/snoguchi/items/87f244a02087fe3fceb5)簡単にデータ型について記載しましたが、今回はAPIから利用する場合のデータ型の指定方法について一覧にしました。

**データ型一覧**

| 値 | データ型 |説明
|:----------- |------------:|------------:
|S|String  |文字列
|N|Number | 数値
|B|Binary  |バイナリ
|SS|String Set  |文字列セット
|NS|Number Set  |数値セット
|BS|Binary Set  |バイナリセット
|L|List|JSON Arrayのように、順番が保たれた値のコレクションを保持
|M|Map|JSON Objectのように、順序付けのないName-Valueペアを保持
|Bool|Boolean|true / false
|NULL|Null|UnknownあるいはUndefinedな値の状態を表す

**２つのデータ型の指定方法**
データ型の指定方法には２種類が存在します。

①直接アルファベットを記載する方法
②awsのsdkを使用する方法

①についてはサンプルスクリプトの通りなので、②について説明します。

ファイルの先頭で
`use Aws\DynamoDb\Enum\Type;`
を行い、以下のように記載すると'S'を指定した事と同様になります。
`Type::STRING`
 
他にもNumberならば
`Type::NUMBER`
とすれば'N'の設定と同意になります。

詳細は[公式リファレンス](http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.DynamoDb.Enum.Type.html)を確認してください。

# query

```php:dynamo.php

//テーブル指定
$get_query_array['TableName'] = 'test3';

//ScanIndexForwardは昇順・降順の指定
$get_query_array['ScanIndexForward'] = true;

//Index名の指定(設定があれば)
$get_query_array['IndexName'] = "local-index";

//キーがidで値がString型でtest1のデータを取得
$get_query_array['KeyConditions']['id']["ComparisonOperator"]='EQ';
$get_query_array['KeyConditions']['id']["AttributeValueList"]=array(array('S'=>'test1'));

//キーがlocalで値がString型でlocal1のデータを取得
$get_query_array['KeyConditions']['local']["ComparisonOperator"]='EQ';
$get_query_array['KeyConditions']['local']["AttributeValueList"]=array(array('S' => "local1"));

//query実行
$result = $client->query($get_query_array);
```

### **Tips**

#### ・ScanIndexForwardについて
`$get_query_array['ScanIndexForward'] = true;` 
上記コードではデータの昇順・降順を指定します。

ScanIndexForward = true (昇順)
ScanIndexForward = false (降順)

#### ・ComparisonOperatorについて

```
//キーがidで値がString型でtest1のデータを取得
$get_query_array['KeyConditions']['id']["ComparisonOperator"]='EQ';
```
上記コードではComparisonOperatorを'EQ'に設定することによって取得条件がイコールのデータを取得する事を意味しています。

ComparisonOperatorは直訳すると「比較演算子」です。
つまりここに「イコール」「以下」「以上」などの値をセットする事によって取得条件を変更する事が可能です。

**ComparisonOperatorの設定値一覧**

| 値 | 意味 |
|:----------- |------------:
|EQ|イコール
|LE|以下
|LT|未満
|GE|以上
|GT|より大きい
|BEGINS_WITH|○○で始まる
|BETWEEN|値1と値2の間

**BETWEENを使う際の注意点**
BETWEENを使う場合、データ型が必ず一致する必要があります。

OK例
('N' => '1')と('N' => '2')でBETWEEN使用
はNumber型での検索なのでOK

NG例
('S' => '1')と('N' => '2')でBETWEEN使用
はString型の1とNumber型の2を検索しようとしているのでNG
 
 
#### ・QueryとScanの違いについて
Queryとほぼ同等の検索機能としてScan機能も提供されていますが、今回はQueryのみサンプルを作成しました。理由としては公式ページに以下のような記述があるようにQueryのほうが検索性能が優れているからです。

> 一般的に、Query オペレーションは Scan オペレーションよりも効率的です。

***Queryの長所***

* Scanでは通常の結果整合性しか利用出来ませんが、Queryの場合は強い結果整合性を利用する事が可能です。

* Scanでは常にテーブル全体がスキャンされるが、Queryではキー条件のセットを持たずに特定の範囲のキーだけが検索されます。

***あえてのScanの長所***
Queryではプライマリキーの属性値だけが検索されるのに対してScanではテーブル内のすべての項目を検索する事が可能です。

プライマリキー以外で検索をかけるのであればScanは有効な手段ですが、テーブル全体スキャンの弊害として、検索負荷が大きい点があり、結果としてテーブルが大きくなるに従ってScanでの検索性能が低くなります。

**個人的な所感**
[公式ページ](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/QueryAndScan.html)に詳細の記載がありますが、あらかじめ設計していればQueryの使用だけで良い気がします。


# putItem

```php:dynamo.php

//テーブル指定
$put_item_array['TableName'] = 'test2';

//stringキーに String型でtestをセット
$put_item_array['Item']['string'] = array('S' => 'test');

//numキーに Number型で1をセット
$put_item_array['Item']['num'] = array('N' => '1');

//putItem実行
$result = $client->putItem($put_item_array);

```


# updateItem

```php:dynamo.php

//テーブル指定
$update_item_array['TableName'] = 'test';

//キーであるidがString型で'test'のデータがupdate対象
$update_item_array['Key']['id'] = array('S' => 'test');

//ActionはPUTで指定
$update_item_array['AttributeUpdates']['value1']['Action'] = 'PUT';

//updateするデータはvalue1にString型でupdateを指定
$update_item_array['AttributeUpdates']['value1']['Value'] = array('S' => 'update');

//updateItem実行
$result = $client->updateItem($update_item_array);

```

### **Tips**

#### ・Actionについて

```
//ActionはPUTで指定
$update_item_array['AttributeUpdates']['value1']['Action'] = 'PUT';
```

上記コードでは更新の種類をPUTとして指定しています。
PUT以外にも以下のような種類があるので一覧にまとめました。


| 値 | 意味 |
|:----------- |------------:
|PUT |属性と値を追加。既に属性があれば新しい値に置換
|ADD|属性と値を追加
|DELETE|削除


# おわりに
今後も気づいた点、修正すべき点あればこの記事はアップデートしていこうと思います。