Axon Step-By-Step : Command Handling

Axon FrameworkReference Guideを見ると、

あまりに多くの事が書いてあって、最初はワケワカメ。

Sample CodeはSpring前提で書いてあるので 動くのは理解できるけど、各Building Blockがどう連携しているのか分かりにくい。

実際は、コアとなるモジュールが拡張しやすい作りになっていて、 そのデフォルトの実装が提供されているだけ。

Springレスで試してみる。

maven2を使ってる場合、pom.xmlに以下のdependencyを追加しておく。

<dependency>
  <groupId>org.axonframework</groupId>
  <artifactId>axon-core</artifactId>
  <version>1.0-rc1</version>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.8.2</version>
  <scope>test</scope>
</dependency>

Command

まずはともあれ、Commandの定義から。 これは、更新系のユースケースをJavaのクラスとして表現する。 今回は更新系の処理を何もしないので、不適切だけど。

/** Command Base Class. */
public abstract class AbstractCommand {
  private String name;
}

public class HelloCommand extends AbstractCommand {}

Test

次に、テストを書く。

テスト的に意味があるのか分からないけど、動作させるのが目的なので。

import org.axonframework.commandhandling.CommandBus;
import org.axonframework.commandhandling.SimpleCommandBus;

public class CommandHandlingTest {
    CommandBus commandBus;

    @Before
    public void setup() {
        commandBus = new SimpleCommandBus(); 
    }

    @Test
    public void handleHelloCommand() {
        HelloCommand command = new HelloCommand();
        command.setName("Axon Framework");
        commandBus.dispatch(command);
    }
}

CommandBus#dispatchで、引数に渡されたCommandに対応するCommand Handlerを実行する。

このテストを実行すると、例外がThrowされる。

org.axonframework.commandhandling.NoHandlerForCommandException: No handler was subscribed to commands of type [HelloCommand]

HelloCommandに対応する、Command Handlerがありませんよ、と。

そりゃそうだ。まだ書いてないもん。

Command Handler

というわけで、Command Handlerを書く。 Command Handlerには対応するCommandを受け取った時のビジネスロジックをガリガリ書く。

実装方法は二つ。

  • A : org.axonframework.commandhandling.CommandHandlerインターフェイスの実装クラスを定義する。
  • B : 引数にCommandを受け取り、かつorg.axonframework.commandhandling.annotation.CommandHandlerアノテーションが付与されているInstace Method(Command Handling Method)をもつクラスを定義する。

基本的には、一クラスに複数のCommandを処理させる事ができる後者のBパターンを選択すると思う。

import org.axonframework.commandhandling.annotation.CommandHandler;

public class SampleCommandHandler {
    @CommandHandler
    public void handle(HelloCommand command) {
        System.out.println(String.format("Hello, %s.", command.getName());
    }
}

この時点でCommandHandlingTestを実行しても、

CommadBusは、HelloCommandとSampleCommandHandlerのhandleメソッドを関連付けられていないため、

先ほどと同様の例外がThrowされる。

Command × Command Handler

AxonのSample Codeでは、SpringのComponent Scanで対応するCommand Handlerを見つけるようだけど、

今回はコードベースでやってみる。

CommandHandlingTest#setupを以下のように変える。

import org.axonframework.commandhandling.CommandBus;

import org.axonframework.commandhandling.SimpleCommandBus;
import org.axonframework.commandhandling.annotation.AnnotationCommandHandlerAdapter;
import org.axonframework.util.Subscribable;

public class CommandHandlingTest {
    CommandBus commandBus;

    @Before
    public void setup() {
      commandBus = new SimpleCommandBus(); 
      SampleCommandHandler target = new SampleCommandHandler();
      Subscribable commandHandler = new AnnotationCommandHandlerAdapter(target, commandBus);
      commandHandler.subscribe();
    }
}

Command Handling MethodをもつPOJOを、AnnotationCommandHandlerAdapterでdecorateし、 同クラスがimplementしているSubscribable#subscribeをcallする。

これだけ。

CommandBus#subscribeは、CommandとCommand Handlerの関連付けを行うMethod。 引数には、ClassとCommandHandlerを受け取る。

AnnotationCommandHandlerAdapterはその名の通り、 CommandHandlerインターフェイスとCommand Handling MethodをもつPOJOとの「Adapter」。

AnnotationCommandHandlerAdapterは、 コンストラクタで渡されたPOJOのCommand Handling Methodの引数の型で CommandBus#subscribeをcallするようにSubscribable#subscribeを実装している。 POJOが複数のCommandに対応するCommand Handling Methodを持っていたとしても、 AnnotationCommandHandlerAdapter#subscribeで一括登録できるので便利。

Testはこんな感じになった。

import org.axonframework.commandhandling.CommandBus;

import org.axonframework.commandhandling.SimpleCommandBus;
import org.axonframework.commandhandling.annotation.AnnotationCommandHandlerAdapter;
import org.axonframework.util.Subscribable;

public class CommandHandlingTest {
    CommandBus commandBus;
    @Before
    public void setup() {
        commandBus = new SimpleCommandBus(); 
        SampleCommandHandler target = new SampleCommandHandler();
        Subscribable commandHandler =
            new AnnotationCommandHandlerAdapter(target, commandBus);
        commandHandler.subscribe();
    }

    @Test
    public void handleHelloCommand() {
        HelloCommand command = new HelloCommand();
        command.setName("Axon Framework");
        commandBus.dispatch(command);
    }
}

このテストを実行すると、標準出力に「Hello, Axon Framework.」と出力される。

It’s simple.

Axonは、CQRSのQueryについては関与しないので、このCommand×Command Handlerがベースとなる。 めちゃシンプル。

重要なのは、Command = 更新系ユースケース、という所。 Javaの静的型付けというシステムを利用して、「顧客要求」を上手く表現した良い例。

ユースケース記述の段階で、依存が少ない状態でコードに反映できる。 コードに反映する事で、各ユースケースの共通項が明確になり、 ユースケースの洗練化に繋がる・・・とハッピーなんだけれど。

次回は、Command HandlerからのCallbackをやる予定。

このエントリーをはてなブックマークに追加
comments powered by Disqus