DynamoDBを簡単に操作するためのインターフェース Kettle/ORMの紹介


# はじめに

DynamoDBにも、AWS SDK for PHPは提供されているのですが、どうも使いにくいです。
というのも、検索や更新のパラメータや取得したデータのフォーマットが配列で、
すべて微妙に違うので覚えられない。つらい。ということで作りました。

https://github.com/inouet/kettle



# 使い方イメージ

ユーザーID 10番の人のデータを取得し、名前を John に更新する例

```php
<?php
use Kettle\ORM;

$user = ORM::factory('User')->findOne(10);
$user->name = 'John';
$user->save();
```

ユーザーID 10番の人のつぶやきをすべて取得して出力する例

```php
<?php
use Kettle\ORM;

$tweets = ORM::factory('Tweet')->where('user_id', 10)
                 ->findMany();

foreach ($tweets as $tweet) {
    echo $tweet->text . PHP_EOL;
}
```

こんな感じに、あまりDynamoDBを意識せずにObjectを操作する感覚で扱えます。

# インストール

composer からインストールできます。

composer.json

```
{
    "require": {
        "kettle/dynamodb-orm": "dev-master"
    }
}
```

1. 上記のようなファイルを composer.json というファイル名で保存。
2. 下記のコマンドを実行してインストールします。 composer.phar は別途インストールしてください。

```
$ composer.phar install
```

vendor 以下にインストールされます。
使うに際には、autoload.php をrequireしておけばクラス使用時に自動的にロードされるようになります。

```
<?php

require_once __DIR__.’/vendor/autoload.php’;

```


# 使い方

## 1. まずは設定

下記のような形で AWSの認証情報をセットします。

```php
<?php
use Kettle\ORM;

ORM::configure("key",    ‘YOUR_AWS_KEY');
ORM::configure("secret", ‘YOUR_AWS_SECRET');
ORM::configure("region", ‘YOUR_AWS_REGION');

// DynamoDB Localを使うには、base_url をセットします。
ORM::configure("base_url", ‘http://localhost:8000');

```

## 2. 次にテーブルに対応するEntityクラスを作成します。

実際に上記使い方イメージのように操作するには Userクラス、Tweetクラスが必要です。
ORMを継承したクラスに、テーブル名、ハッシュキー、レンジキー、カラムを定義します。


```php
<?php

use Kettle\ORM;

class User extends ORM {
    protected $_table_name = 'user';
    protected $_hash_key   = 'user_id';
    protected $_schema = array(
      'user_id'    => 'N',  // user_id is number
      'name'       => 'S',  // name is string
      'age'        => 'N',
      'country'    => 'S',
     );
}

class Tweets extends ORM {
    protected $_table_name = ‘tweets’;
    protected $_hash_key   = 'user_id';
    protected $_range_key  = ‘timestamp’;

    protected $_schema = array(
      'user_id'    => 'N',  
      ‘timestamp’  => 'S', 
      ‘text’       => ’S’,
      ‘is_deleted’ => ’N’,
     );
}

```

DynamoDBはスキーマレスなので、定義とか必要なくなんでも突っ込めるのですが、
コードをメンテナンスする際に、データに何が入っているか把握できないと保守性が下がるため
上記のように定義しておく形を取っています。

Userクラスを使用する際には、次のようにfactoryメソッドを使いインスタンスを生成します。

```php
<?php

$user = ORM::factory('User’);
```

## 3. データ操作（データの登録）

1. create メソッドで空のインスタンスを作成
2. 値を代入
3. save() 


```php
<?php

$user = ORM::factory('User')->create();
$user->id = 1;
$user->name = 'John';
$user->age  = 20;
$user->save();

```

## 4. データの操作（１レコードの取得）

1. findOne にHashKeyの値を指定します。

```php
<?php

$user = ORM::factory('User')->findOne(1);
echo $user->name. PHP_EOL;

// asArray() で連想配列になります。
print_r($user->asArray());
```

HashKeyとRangeKeyからなるテーブルの場合は両方を渡します。

```
$hoge = ORM::factory(‘Hoge’)->findOne(1, ‘fuga’);
```

## 5. データの操作（更新）

1. findOneでデータを取得します。
2. 新しいデータをセットします。
3. save() 


```php
<?php

$user = ORM::factory('User')->findOne(1);
$user->age = 21;
$user->save();
```

## 6. データの操作（検索）

注意：
DynamoDBでは、インデックスを張ったカラムでしか検索ができません。
HashKey, RangeKey、Local Secondary Index、Global Secondary Index
などを事前に作成しておく必要があります。

1. whereメソッドを使い条件を指定します。
2. findMany で取得します。

```php
<?php

$tweets = ORM::factory('Tweets')
        ->where('user_id', 1)
        ->where('timestamp', '>', 1397264554)
        ->findMany();

foreach ($tweets as $tweet) {
     echo $tweet->text . PHP_EOL;
}

```

* whereメソッドは、引数が２つの時には、第1引数がkey名、第2引数が値になり、完全一致の検索となります。
* 引数が3つの場合には、第1引数がkey名、第2引数が比較演算子、第3引数が値となります。
* 比較演算子は、”=“, “<=“, “>“, “BETWEEN” などの一般的なものが使えます。

## 7. データの操作（GSIを使った検索）

Global Secondary Index を使って検索するには、下記のように
index() メソッドでインデックス名を指定します。


```php
<?php

$users = ORM::factory('User')
        ->where('country', 'Japan')
        ->where('age', '>=', 20)
        ->index('country-age-index')  // インデックス名を指定
        ->findMany();

```

## 8. データの操作（検索＋クエリフィルタリング）

前述のとおり、DynamoDBはインデクスを指定した検索しかすることができませんが、
クエリフィルタリングという形で、検索した結果をフィルタして取得することができます。

下記の例は、is_deleted が 0 のものだけに絞って取得しています。

```php
<?php

$tweets = ORM::factory('Tweets')
          ->where('user_id', 1)
          ->filter('is_deleted', 0) // using filter
          ->findMany();

```

# 進んだ使い方

## 1. シーケンスを管理するテーブルを作る

DynamoDBでは、MySQLのauto_incrementのように自動でカウントアップするカラムは作れませんが、工夫でauto_increment的な動きをするカラムを作ることができます。

sequence というテーブルを作っておきます。
HashKey は name (S) です。

```php
<?php

use Kettle\ORM;

class Sequence extends ORM
{
    protected $_table_name = "sequence";
    protected $_hash_key   = "name";

    protected $_schema = array(
        'name'  => 'S',
        'count' => 'N',
    );

    public function getNextId($name)
    {
        $this->name = $name;
        $values  = ['count' => 1];
        $options = ['Action' => ['count' => 'ADD'], 'ReturnValues' => 'UPDATED_NEW'];
        $result = $this->updateItem($values, $options);
        $count = $result->getPath('Attributes/count/N');
        return $count;
    }
}
```

こんなクラスを作っておいて、

```php
<?php

$user_id = ORM::factory('Sequence')->getNextId('user_id');
var_dump($user_id);

```

こんな感じで新しいIDを取り出します。何度も呼び出すとカウントアップされているのが分かります。

user_id の部分は、任意の文字列でOKです。


# おわりに

バグ等ありましたら Githubの方に報告をお願いします。
https://github.com/inouet/kettle