appengine-deferred 0.1 作成 #appengine

appengine-deferred を作成しました。
https://github.com/vvakame/appengine-deferred

DeferredTask について

appengineの1.4.3からDeferredTaskという、RunnableとSerializableのサブタイプなものが追加されました。
これは、DeferredTaskをimplementしたクラスをTaskOptionのpayloadとして渡してTQを起動すると、なんかまぁ /_ah/queue/__deferred__ だかのTQとして起動される、みたいなControllerタイプではない起動の仕方のTQです。多分。

作ったもの

特定のメソッドに @Deferred をつけておくと、例外発生時に自動的にTQでの実行に切り替えてくれるライブラリを作りました。
注意事項がいくつかあるのですが、まぁ後にします。

なんで作ったか

appengine用のコードを書いていて、ある処理を冪等にするために、失敗時には非同期でリトライにする、というコードを何回も書いてきました。全体の作業時間の10%程度を占めている作業なんでないかなーと思います。
僕は、以前からある特定の処理を自動的に非同期的に実行してくれるように変換できないかなー…と思っていましたが、Controller型のTQをベースに考えると、出来なくもないけど使い方も難しく、ライブラリ自体の開発コストが多分50時間超えるだろうなー、って感じでした。
ですが、1.4.3からはDeferredTaskが追加されたのでまぁいっちょ自動化してみるかーっというわけで作りました。だいたい12時間ぐらいかかった気がします。

使い方

APTライブラリなので、APTのjarを適当にSlim3っぽく指定してください。
mvnリポジトリも一応用意してあるので、最新の appengine-deferred-usage のpom見てください。

非同期化したいメソッドに @Deferred つけるだけです。
appengine-deferred/SampleService.java at 3d651162457ed61cf9b17bd86287736d27ab0d00 · vvakame/appengine-deferred · GitHub

利用側
appengine-deferred/SampleServiceTest.java at 3d651162457ed61cf9b17bd86287736d27ab0d00 · vvakame/appengine-deferred · GitHub

アノテーションを付けたメソッドが属するクラスが SampleService とすると、自動的に SampleServiceDeferred というクラスができ、同名、同シグニチャのメソッドが定義されるので、それを呼び出すようにしてください。

想定されている例外について、DeferredUtil.throwWithValue(...) 経由で再throwすると、XxxDeferredクラスの方でキャッチされ、自動的に非同期で再実行されます。
呼び出し側ではthrowWithValueに一緒に渡した値が返ってくるので、処理が成功したか失敗したかは気にせず処理を継続することができます。
例外発生時に DeferredUtil.throwWithValue(...) で処理しなかった場合、想定外の例外ということで非同期化せず、普通に呼び出し元に伝播します。

あと、特定の例外が発生した時にメール送りたいなー、みたいな時のためになんかまぁ独自処理を追加できるようにしました。
SampleService.ownTask() とか、その辺みてください。

注意

冪等であるかを強く意識しつつ利用しないと、データの整合性が簡単に壊れちゃったりする気がします。
DeferredTaskはSerializeして投げられるので、呼出し元から渡される引数のインスタンスの状態を変更するようなメソッドを自動で非同期化すると、冪等にしたつもりでなってなくてうぎゃー!とかなったりすると思います。引数はImmutableであると考えてコードを書いたほうがいいと思います。

あとがき

多分僕が自分で使って幸せになる!ぐらいしか考えてないので、説明がだいぶ適当だと思います。
使ってみたい人がいたら、分からないところがあったら聞いてくれれば多分答えます。