Hatena::ブログ(Diary)

高卒文系プログラマの日常 by zetta1985 このページをアンテナに追加 RSSフィード Twitter

2011-03-02

Axon Step-By-Step : Command Callback

前回は、Axon Frameworkの根幹であるCommand Handlingを試した。

今回は、Command HandlerからのCallback(戻り値の取得)を試してみる。


前回のSampleCommandHandlerクラスを以下のように書き換える。

import org.axonframework.commandhandling.annotation.CommandHandler;

public class SampleCommandHandler {

	@CommandHandler
	public String handle(HelloCommand command) {
		return String.format("Hello, %s.", command.getName());
	}
}

標準出力していた処理を戻り値として返すようにしただけ。


次に、Command Handlerの戻り値を取得するCommandCallbackインターフェイスの実装クラスを定義する。

import org.axonframework.commandhandling.CommandCallback;

/**
 * @author t_hara
 */
public class SampleCallback implements CommandCallback<String> {

	private String result;
	private Throwable cause;
	
	@Override
	public void onFailure(Throwable cause) {
		this.cause = cause;
	}

	@Override
	public void onSuccess(String result) {
		this.result = result;
	}

	public String getResult() {
		return result;
	}

	public Throwable getCause() {
		return cause;
	}
}

CommandCallback#onSuccessは、Command Handlerが正常終了した時に呼ばれ、

その戻り値が引数として渡される。

一方、CommandCallback#onFailureは、Command Handlerが例外をThrowした時に呼ばれ、

その例外オブジェクトが引数として渡される。


で、Testを以下のように書き換える。

import org.axonframework.commandhandling.CommandBus;
import org.axonframework.commandhandling.SimpleCommandBus;
import org.axonframework.commandhandling.annotation.AnnotationCommandHandlerAdapter;
import org.junit.Before;
import org.junit.Test;

public class CommandHandlingTest {

	CommandBus commandBus;
	
	@Before
	public void setup() {
		commandBus = new SimpleCommandBus();
		
		new AnnotationCommandHandlerAdapter(
				new SampleCommandHandler(), commandBus).subscribe();
	}
	
	@Test
	public void handleHelloCommand() {
		HelloCommand command = new HelloCommand();
		command.setName("zetta1985");
		
		SampleCallback callback = new SampleCallback();
		commandBus.dispatch(command, callback);
		
		assertEquals("Hello, zetta1985.", callback.getResult());
		assertNull(callback.getCause());
	}
}

CommandBus#dispatchの第二引数にCommandCallback実装クラスのインスタンスを渡す事で、

Command Handlingメソッドの戻り値を取得する事ができる。


ちなみに、Command Handlerが別スレッドで動く場合の戻り値取得用として

java.util.concurrent.Futureインターフェイスを実装したFutureCallbackというクラスが提供されている。*1


CommandCallback実装クラスで指定した型パラメータと実際のCommand Handlerが返す値の型が違う場合、

当然のようにClassCastExceptionが発生するので注意。


また、CommandBus#dispatch(command, callback)で実行されたCommand Handlerでthrowされた例外は

Callbackに渡されてしまうため、CommandBus#dispatchを呼び出した側は例外が発生したかどうかを確認するために

何らかの方法で例外オブジェクトを取得する必要がある。


この例だと、SampleCallback#getCauseの戻り値がnullである事を検証し

Command Handlerで例外が発生していない事を検証しているが、実際に使ってみると非常にメンドクサイ。

戻り値毎にCallbackクラスを定義するのも冗長だし、

だからといって、CommandBus#dispatchのたびに匿名クラスを定義するのも冗長。

さらに、Reference Guideでは

CommandBus#dispatchの呼び出し側の後続処理がCommand Handlerの結果に依存する事は、

「a highly discouraged approach」*2とされている。


なので、基本的にはCallbackでCommand Handlerの戻り値を取得しない、というポリシーで。

CQRS的には、それが自然。

Commandは更新処理専用、何らかのデータを得る場合はQueryを使いましょう、という事ですな。


余談になるけれど、個人的にはCommandBus#dispatchを直接呼ぶのではなく、

CommandBus#dispatchに処理を委譲するFacadeを使う事を推奨したい。

Aside

まず、Facadeとなるインターフェイスを定義する。

public interface CommandDispatcher {
	void dispatch(Object command);
}

このインターフェイスは、CommandBus#dispatchの呼び出しに必要なCallbackの知識をカプセル化する。

また、このインターフェイスを介す事により、Command要求側がAxon Frameworkに依存しなくなる。*3


ここでは、戻り値を返さないdispatchメソッドのみを宣言しているが、

Commandの型によって戻り値が異なるdispatchメソッドを定義するなど、

Command要求側の要件にあったメソッドを用意してもいい。



そして、その実装クラスであるDefaultCommandDispatcherクラスを定義する。

import org.apache.commons.lang.Validate;
import org.axonframework.commandhandling.CommandBus;
import org.axonframework.commandhandling.callbacks.VoidCallback;
import org.zetta1985.framework.CommandDispatcher;

public class DefaultCommandDispatcher implements CommandDispatcher {

	private final CommandBus commandBus;
	
	/**
	 * @param commandBus
	 */
	public DefaultCommandDispatcher(CommandBus commandBus) {
		super();
		Validate.notNull(commandBus);
		this.commandBus = commandBus;
	}

	@Override
	public void dispatch(Object command) {
		Validate.notNull(command);
		commandBus.dispatch(command, new DefaultCallback());
	}
	
	static class DefaultCallback extends VoidCallback{
		
		@Override
		protected void onSuccess() {
			// do nothing.
		}
		
		@Override
		public void onFailure(Throwable cause) {
			throw (cause instanceof RuntimeException) 
				? (RuntimeException)cause : new CommandDispatchException(cause);
		}
	}
}

CommandDispatcher#dispacthの実装で、DefaultCallbackクラスをインスタンス化、CommandBus#dispacthの第2引数に渡す。

DefaultCallbackにより、Command Handlerの結果による挙動は以下のようになる。

  • 正常終了時 : 何もしない。
  • 非検査例外発生時 : そのままthrow
  • 検査例外発生時 : RuntimeExceptionサブクラスのCommandDispatchException(別途定義)でラップしてthrow

これで、CommandBus#dispatchを呼び出すたびにわざわざCallbackをインスタンス化する必要が無くなった。


Summary

OpenSourceのFrameworkは、基本的にはどれも汎用的に作られているが、

実際にそのFrameworkを使ってアプリケーションを作る場合には、

その汎用性の高さが仇になる事もしばしば。

そう言った場合には、アプリケーション要件にあった独自実装を軽くラップする事で、

各アプリケーション固有の要件に上手く適合させる事が重要かと。


Axon Frameworkは、実はその点においても優れているのだけれど、それは追々書くとして

次回は CQRS & EventSourcing の真骨頂、Aggregateについて。

*1:自作できるぐらいのシンプルさだけどw

*2:超意訳 : メチャクチャがっかりアプローチ

*3:これがメリット一番でかいかも