この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
4月から入社した新入社員のお仕事がいきなりAmazon SimpleWorkflowServiceを使って開発するなんてことがあるかも知れません。
もしそうなった時にいきなり何も無しに始めろと言われてもすこしとっつきづらい事間違いないサービスAmazon SimpleWorkflowService通称SWFの最初の一歩となれればと思いこの記事を書きます。
概念的な物は日本語の良記事がありますので、こちらも同時に読んでもらえばよりわかりやすいと思います。
概念が何となくわかったら次のステップとして、AWS SDK for Java についてくるSampleを動かしてみて、
動かしたら次は少しだけ改変して動きを確かめるという事の繰り返しが一番理解の助けになるはずです。
サンプルの実行準備
まずはサンプルを単純に実行してみます。
AWS SDK for Javaは2013年4月8日現在最新版の1.4.1を使います
AWS SDK for Javaを解凍して、そのままのディレクトリ構成で動かすことが前提となっているようです。
$ unzip aws-java-sdk-1.4.1.zip
$ cd aws-java-sdk-1.4.1
$ ls -la
drwxr-xr-x 9 ec2-user ec2-user 306 3 15 12:15 .
drwxr-xr-x 4 ec2-user ec2-user 136 4 4 17:32 ..
-rw-r--r--@ 1 ec2-user ec2-user 10587 3 15 12:15 LICENSE.txt
-rw-r--r--@ 1 ec2-user ec2-user 591 3 15 12:15 NOTICE.txt
-rw-r--r--@ 1 ec2-user ec2-user 3709 3 15 12:15 README.html
drwxr-xr-x 3 ec2-user ec2-user 102 3 15 12:15 documentation
drwxr-xr-x 6 ec2-user ec2-user 204 3 15 12:15 lib
drwxr-xr-x 12 ec2-user ec2-user 408 3 23 16:17 samples
drwxr-xr-x 13 ec2-user ec2-user 442 3 15 12:15 third-party
$ cd samples/AwsFlowFramework/
この中にREADME.htmlがあるので、それを見てもらえればひと通りのサンプルの実行方法も書いてあります。
configファイル
SWFにアクセスするためのACCESS_KEYやSECRET_KEYなどの設定をするファイルを作成する必要があります。
$AWS_SDK_HOME/samples/AwsFlowFramework/access.propertiesというファイルを編集します。
####### SWF PROD ENDPOINT #######
service.url=http://swf.ap-northeast-1.amazonaws.com
# Fill in your AWS Access Key ID and Secret Access Key for SWF
# http://aws.amazon.com/security-credentials
AWS.Access.ID=XXXXXXXXXXXXXXXXXXXX
AWS.Secret.Key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS.Account.ID=000000000000
# Fill in your AWS Access Key ID and Secret Access Key for S3
# http://aws.amazon.com/security-credentials
# S3を使ったサンプルを動かす時に必要ですが、今回は編集しなくても大丈夫です。
S3.Access.ID=XXXXXXXXXXXXXXXXXXXX
S3.Secret.Key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
S3.Account.ID=000000000000
######## COMMON CONFIG ###########
domain=Samples
domainRetentionPeriodInDays=1
# これ以降の設定も今回は必要ありません。
####### FileProcessing Sample Config Values ##########
Activity.Worker.LocalFolder=tmp/
〜 略 〜
このaccess.propertiesというファイルが置いてあるディレクトリをAWS_SWF_SAMPLES_CONFIGという環境変数に追加しておくと、楽です。
ダウンロードしてきた状態では、Apache antでの実行をするようになっているので、antが使えない場合はantも使えるようにして下さい。
Domainの作成
ManagementConsoleからconfigで設定したregionにDomainを作成して下さい。
Domain名はdomain=の項目で設定したDomain名になります。
もし設定していないと
Exception in thread "main" UnknownResourceException: Status Code: 400, AWS Service: AmazonSimpleWorkflow, AWS Request ID: 838914ed-a1f3-11e2-9824-87262ed91ff2, AWS Error Code: UnknownResourceFault, AWS Error Message: Unknown domain: Samples
というエラーが出ます。
サンプルの実行
SWFを使うには2つの待ち受けているプロセスと、1つの実行の指示を出すプロセスがあります。
サンプルでは
- ActivityHost
- WorkflowHost
- WorkflowExecutionStarter
この3つとなっています。
この3つは“はじめに”で紹介したエントリにある図を借りて対応表を作ると
図 | サンプル |
---|---|
starter | WorkflowExecutionStarter |
decider | WorkflowHost |
worker | ActivityHost |
となります。
この3つそれぞれに実行用のスクリプトを作っておきましょう。
それぞれの役割と動かし方を説明していきます。
ActivityHost
ActivityHostはActivity(実際の作業)を動かすプロセスになります。
HelloWorldActivityHost.sh
export AWS_SWF_SAMPLES_CONFIG=$(pwd)
ant -f build.xml -Dmain-class="com.amazonaws.services.simpleworkflow.flow.examples.helloworld.ActivityHost" run
WorkflowHost
WorkflowHostはActivityの実行を制御するプロセスになります。
今回のHelloWorldでは余り複雑な動きはしませんが、途中で何かの承認を得るといったようなワークフローを実行する場合このWorkflowHostが制御してくれます。
SWF用語で言うとDeciderと呼ばれる存在です。
HelloWorldWorkflowHost.sh
export AWS_SWF_SAMPLES_CONFIG=$(pwd)
ant -f build.xml -Dmain-class="com.amazonaws.services.simpleworkflow.flow.examples.helloworld.WorkflowHost" run
WorkflowExecutionStarter
WorkflowExecutionStarterは待ち受けるようなプロセスではありません。
Workflowを開始するためのまさにStarterです。
サンプルなので、個別に実行出来るようになっていますが、他のシステムから呼び出して使うことでWorkflowを開始させたりもします。
HelloWorldWorkflowExecutionStarter.sh
export AWS_SWF_SAMPLES_CONFIG=$(pwd)
ant -f build.xml -Dmain-class="com.amazonaws.services.simpleworkflow.flow.examples.helloworld.WorkflowExecutionStarter" run
ActivityHost,WorkflowHostを実行してからWorkflowExecutionStarterを実行すると、ActivityHostのコンソールにHelloWordと表示しているのが見えると思います。
これでひとまず、サンプルの実行は完了です。
色々な実験
さて、ここからが今回の本番です。 色々と試してSWFの動きの理解を深めて行きましょう。
Desiderの動きを表示する
package com.amazonaws.services.simpleworkflow.flow.examples.helloworld;
/**
* Implementation of the hello world workflow
*/
public class HelloWorldWorkflowImpl implements HelloWorldWorkflow{
HelloWorldActivitiesClient client = new HelloWorldActivitiesClientImpl();
@Override
public void helloWorld(String name) {
client.printHello(name);
}
}
サンプルではhelloWorkdWorkflowImpl.javaは上のようになっていますが、これを少しだけ改変して、どのようになるか確かめて見ます。
package com.amazonaws.services.simpleworkflow.flow.examples.helloworld;
/**
* Implementation of the hello world workflow
*/
public class HelloWorldWorkflowImpl implements HelloWorldWorkflow{
HelloWorldActivitiesClient client = new HelloWorldActivitiesClientImpl();
@Override
public void helloWorld(String name) {
System.out.println(name);
client.printHello(name);
}
}
とします。
System.out.println(name);
の1行を付け加えてもう一度WorkflowHostをコンパイルしなおし実行します。
$ ant compile
$ ./HelloWorldWorkflowHost.sh
WorkflowHostを実行しなおして、もう一度ExecutionStarterの実行すると、今度はActivityHostの画面だけでなくWorkFlowHostの画面にも“User”と表示されます。
これでExecutionStarterが実行されるとWorkflowImplが実行されてAcitivityが実行される事が解かると思います。
ただ、一つ疑問がでるかも知れません。
ExecutionStarterを一回しか実行していないはずなのに、なぜか2回表示されていませんか?
なぜ2回表示されるかと言うと、ExecutionStarterが実行されて、DeciderがWorkerに処理を振るタイミングと、Activityの処理が完了したタイミングの2回呼ばれているからです。
試しに、コードを下記のように変更してみてください。
package com.amazonaws.services.simpleworkflow.flow.examples.helloworld;
/**
* Implementation of the hello world workflow
*/
public class HelloWorldWorkflowImpl implements HelloWorldWorkflow{
HelloWorldActivitiesClient client = new HelloWorldActivitiesClientImpl();
@Override
public void helloWorld(String name) {
System.out.println(name);
client.printHello(name);
client.printHello(name);
}
}
今度は3回表示されるはずです。
これで、Workflowは何かあるたびに呼び出されているというのがわかると思います。
実行の確認
今度はHellowWorldWorkflowImple.javaを下記のように変更します。
public class HelloWorldWorkflowImpl implements HelloWorldWorkflow {
HelloWorldActivitiesClient client = new HelloWorldActivitiesClientImpl();
@Override
public void helloWorld(String name) {
System.out.println(name);
Promise<Void> promise = client.printHello(name);
AndPromise ap = new AndPromise(promise);
processResults(ap);
}
@Asynchronous
void processResults(AndPromise results) {
System.out.println("processResults");
Promise<?>[] resultArray = results.getValues();
for (Promise<?> result : resultArray) {
if (result.isReady() == true) {
System.out.println("実行完了");
break;
}
}
}
}
変更点はclient.printHello(name);の戻り値をPromiseで受け取ります。
Promiseは非同期なプログラミングにおける有名なデザインパターンの一つなので、ここであえての説明はしません。
AndPromiseは引数にとったPromiseがすべて完了した時に呼び出されます。
今回はpromiseを一つしか受け取っていないので、この一つの処理が終わったら呼び出されます。
今度は処理を3つに増やして実験してみます。
public class HelloWorldWorkflowImpl implements HelloWorldWorkflow {
HelloWorldActivitiesClient client = new HelloWorldActivitiesClientImpl();
@Override
public void helloWorld(String name) {
System.out.println(name);
Promise<Void> promise = client.printHello(name);
Promise<Void> promise2 = client.printHello(name);
Promise<Void> promise3 = client.printHello(name);
AndPromise ap = new AndPromise(promise,promise2,promise3);
processResults(ap);
}
@Asynchronous
void processResults(@Wait AndPromise results) {
System.out.println("processResults");
System.out.println("実行完了");
}
}
上記のように改変して実行しなおし、ExecutionStarterを実行すると以下のような出力がWorkflowHostにあるはずです。
User
User
User
User
processResults
実行完了
このコトからAndPromiseを引数に取っているprocessResultsはすべてのpromiseが帰ってきた時に実行されていることがわかると思います。
耐障害性の実験
次は耐障害性の実験をしてみます。
この実験ではソースコードの改変はしません。
以下の手順で実験します。
- まず2つの動いたままのプロセスのうち、WorkflowHostのプロセスを止めてみます。
- WorkflowHostが停止している状態で、ExecutionStarterを実行します。
- いくら待ってもActivityHostに出力されません。
- WorkflowHostを再び実行します。
- しばらくするとActivityHostにUserと出力されるのを確認します。
同様にActivityHostだけを止めてExecutionStarterを実行もしてみて下さい。
当たり前ですが、ActivityHostには何も出力されません。
WorkflowHostには表示されているはずです。
ここで、ActivityHostを実行したらどうなると思いますか?
勘のいい人なら、実行した途端に表示されると思うのでは無いでしょうか?
残念ながらそうはいきません。
ここで、ManagementConsoleを見てみましょう。
NavigationからWorkflow Executionsをクリックすると実行のログを確認することができます。
なんと失敗しています。 これじゃ、desiderは落とせてもworkerは落とせませんね
失敗した場合の対処方
耐障害性の実験でAcitivityHostが止まっていた場合には失敗する事がわかりました。
失敗した場合でも、それを検知する事が出来れば、色々と対応できます。
HelloWorldWorkflowImple.javaを下記のように変更します。
ここで新しく出てきたのはTryCatchというAbstractクラスです。doTryというメソッドの実装にWorkflowの実装を書きます。
doCatchに失敗した時の処理を記述します。
ActivityHostを止めてから
WorkflowHostをもう一度compileして、実行しなおします。
public class HelloWorldWorkflowImpl implements HelloWorldWorkflow {
HelloWorldActivitiesClient client = new HelloWorldActivitiesClientImpl();
@Override
public void helloWorld(String name) {
final String n = name;
TryCatch f = new TryCatch() {
@Override
protected void doTry() throws Throwable {
System.out.println(n);
Promise<Void> promise = client.printHello(n);
Promise<Void> promise2 = client.printHello(n);
Promise<Void> promise3 = client.printHello(n);
AndPromise ap = new AndPromise(promise,promise2,promise3);
processResults(ap);
}
@Override
protected void doCatch(Throwable arg0) throws Throwable {
System.out.println("実行に失敗しました。");
}
};
}
@Asynchronous
void processResults(AndPromise results) {
System.out.println("processResults");
System.out.println("実行完了");
}
}
User
User
User
User
実行に失敗しました。
と表示されるはずです。
これで実行失敗した時の処理方法もわかりました。
失敗した時にリトライするもよし、他の処理をするもよし、ワークフローにあわせて処理を記述してみて下さい。
さいごに
SWFは他のAWSのサービスとくらべて扱い方が非常に難しいサービスだと思います。しかし、使いこなせば非常に強力です。
今回の記事でいくつかの実験をして来ましたが、SWFを使いはじめる最初の一歩として、サンプルコードを改造して、色々な動きを確かめてみると複雑なワークフローも簡単に記述できることが解かるかと思います。
他にも色々な機能がありますので、是非色々と試してみてください。