SDK によって提供されている高次の抽象化のひとつに、 waiter の概念があります。
Waiter によって 結果整合性 (eventually consistent) システムを簡単に利用することができます。
それは、 Waiter がリソースが特定の状態になるまでリソースをポーリングすることが容易にしてくれるためです。
クライアントの docblock を見ることで、クライアントによってサポートされている waiter のリストを知ることができます。
"waitUntil" で始まっている @method タグは waiter を使うことができます。
$client->waitUntilBucketExists(array('Bucket' => 'my-bucket'));
先のメソッド呼び出しで waiter オブジェクトをインスタンス化し、 bucket が存在するまでポーリングすることができます。
Waiterがバケットに対して過剰にポーリングを行うと、
Aws\Common\Exception\RuntimeException 例外が発生します。
"waitUntil[…]" 型のメソッドはいずれも __call マジックメソッドを使って実装されています。
実際に存在するのは waitUntil() メソッドだけですが、 @method 記法で
定義されたメソッドは多くのIDEが自動補完できるので、
waitUntil[…]" 型のメソッドは目につきやすいのです。
次のコードは waitUntil() メソッドを使っていますが、先のコード例と同等です。
$client->waitUntil('BucketExists', array('Bucket' => 'my-bucket'));
Waiterが実行を試みるポーリングの回数や、各ポーリング間の遅延秒数は、 "waiter" というプレフィックスがついたキーの値を渡して調整できます。
$client->waitUntilBucketExists(array(
'Bucket' => 'my-bucket',
'waiter.interval' => 10,
'waiter.max_attempts' => 3
));
Waiterオブジェクトと直接やり取りするためには、 getWaiter() メソッドを使います。
次のコードは前節に示した例と同等です。
$bucketExistsWaiter = $client->getWaiter('BucketExists')
->setConfig(array('Bucket' => 'my-bucket'))
->setInterval(10)
->setMaxAttempts(3);
$bucketExistsWaiter->wait();
Waiterオブジェクトを直接操作する方法には、イベントリスナをアタッチできる、という利点があります。 Waiterは ウェイトサイクル ごとに、2つのイベントを送出します。 ウェイトサイクルでは次のような処理を行います。
waiter.before_attempt イベントをディスパッチする。max_attempts に達すれば例外を投げる。waiter.before_wait イベントをディスパッチする。interval 秒間スリープする。Waiterオブジェクトは Guzzle\Common\AbstractHasDispatcher クラスを拡張したものなので、 addSubscriber() メソッド、 getEventDispatcher() メソッドがあります。
リスナをアタッチするコード例を示します。
これは先に示した例を修正したものです。
// Get and configure the waiter object
$waiter = $client->getWaiter('BucketExists')
->setConfig(array('Bucket' => 'my-bucket'))
->setInterval(10)
->setMaxAttempts(3);
// Get the event dispatcher and register listeners for both events emitted by the waiter
$dispatcher = $waiter->getEventDispatcher();
$dispatcher->addListener('waiter.before_attempt', function () {
echo "Checking if the wait condition has been met…\n";
});
$dispatcher->addListener('waiter.before_wait', function () use ($waiter) {
$interval = $waiter->getInterval();
echo "Sleeping for {$interval} seconds…\n";
});
$waiter->wait();
ユースケースを考慮すれば、アプリケーション特有のWaiterロジック、
あるいはSDKに組み込まれていないWaiterが必要である場合、
独自のWaiterオブジェクトを実装するとよいでしょう。
クライアントの getWaiterFactory() メソッド、 setWaiterFactory() メソッドで、
クライアントが使うWaiterファクトリに手を加え、独自のWaiterが生成されるようにします。
サービスクライアントは通常、 Aws\Common\Waiter\CompositeWaiterFactory を使うので、必要ならばファクトリを追加することも可能です。
独自のWaiterクラスを実装するとともに、クライアントのWaiterファクトリに手を加えて、
独自のWaiterが生成されるようにした例を示します。
namespace MyApp\FakeWaiters
{
use Aws\Common\Waiter\AbstractResourceWaiter;
class SleptThreeTimes extends AbstractResourceWaiter
{
public function doWait()
{
if ($this->attempts < 3) {
echo "Need to sleep…\n";
return false;
} else {
echo "Now I've slept 3 times.\n";
return true;
}
}
}
}
namespace
{
use Aws\S3\S3Client;
use Aws\Common\Waiter\WaiterClassFactory;
$client = S3Client::factory();
$compositeFactory = $client->getWaiterFactory();
$compositeFactory->addFactory(new WaiterClassFactory('MyApp\FakeWaiters'));
$waiter = $client->waitUntilSleptThreeTimes();
}
これを実行すると次のような出力が得られます。
Need to sleep… Need to sleep… Need to sleep… Now I've slept 3 times.
SDKに付属のWaiterは、クライアント向けのサービス記述にその定義があります。 設定DSL (ドメイン固有の言語) を使って、ウェイト間隔、ウェイト条件、 リソースを検査またはポーリングして条件を解決する方法を定義しています。
このデータは、クライアントのインスタンスを生成する際、
Aws\Common\Waiter\WaiterConfigFactory クラスが自動的に取り込んで使うようになります。
サービス記述で定義されたWaiterを、クライアントが使えるようにするためです。
Amazon Glacierのサービス記述 (Aws\Glacier\GlacierClient に付属しているWaiterを定義) の
抜粋を示します。
return array(
// ...
'waiters' => array(
'__default__' => array(
'interval' => 3,
'max_attempts' => 15,
),
'__VaultState' => array(
'operation' => 'DescribeVault',
),
'VaultExists' => array(
'extends' => '__VaultState',
'success.type' => 'output',
'description' => 'Wait until a vault can be accessed.',
'ignore_errors' => array(
'ResourceNotFoundException',
),
),
'VaultNotExists' => array(
'extends' => '__VaultState',
'description' => 'Wait until a vault is deleted.',
'success.type' => 'error',
'success.value' => 'ResourceNotFoundException',
),
),
// ...
);
Waiterを開発して、SDK に対して貢献するためには、 waiters DSL を使って実装する必要があります。 DSL はまだドキュメント化されていません。現在のテーマを変更し、waiter の実装を手助けしたい場合は、 GitHub 経由で問い合わせてください。