1概要

この記事では、http://www.axonframework.org/[Axon]フレームワークと、それがhttps://martinfowler.com/bliki/CQRS.html[

CQRS

に基づいたアーキテクチャの実装にどのように役立つかを見ていきます。](Command Query Responsibility Segregation)および潜在的にイベントソーシング。

これらの概念の多くはhttps://en.wikipedia.org/wiki/Domain-driven__design[DDD]からすぐに出てくることに注意してください。これはこの記事の範囲を超えています。


2 Mavenの依存関係

サンプルアプリケーションの作成を始める前に、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.axonframework%22%20AND%20a%3A%22axon-を追加する必要があります。 core%22[axon-core]およびhttps://search.maven.org/classic/#search%7C1%7Cg%3A%22org.axonframework%22%20AND%20a%3A%22axon-test%22[axon-test]の

pom.xml

への依存関係:

<dependency>
    <groupId>org.axonframework</groupId>
    <artifactId>axon-core</artifactId>
    <version>${axon.version}</version>
</dependency>
<dependency>
    <groupId>org.axonframework</groupId>
    <artifactId>axon-test</artifactId>
    <version>${axon.version}</version>
    <scope>test<scope>
</dependency>

<properties>
    <axon.version>3.0.2</axon.version>
</properties>


3メッセージサービス – コマンド

システム内でCQRSを実行するという最初の目的で、ユーザーが実行できる2種類のアクションを定義します。

  1. 新しいテキストメッセージを作成する

  2. テキストメッセージを既読にする

当然、これらは私たちのドメインをモデル化する2つのコマンド

CreateMessageCommand

と__MarkReadMessageCommandになります。

public class CreateMessageCommand {

    @TargetAggregateIdentifier
    private String id;
    private String text;

    public CreateMessageCommand(String id, String text) {
        this.id = id;
        this.text = text;
    }

   //...
}
public class MarkReadMessageCommand {

    @TargetAggregateIdentifier
    private String id;

    public MarkReadMessageCommand(String id) {
        this.id = id;
    }

   //...
}



TargetAggregateIdentifier


注釈は、注釈付きフィールドが指定された集約のIDであることをAxonに伝えます。集計についてはすぐに触れます。


4イベント

私たちの集合体は

MessageCreatedEvent



MessageReadEvent

イベントを生成することによって上記で作成されたコマンドに反応するでしょう:

public class MessageCreatedEvent {

    private String id;
    private String text;

   //standard constructors, getters, setters
}

public class MessageReadEvent {

    private String id;

   //standard constructors, getters, setters
}


5集約 – コマンドに関するイベントの生成

コマンドをモデル化したので、コマンドのイベントを生成するハンドラを作成する必要があります。

集約クラスを作成しましょう。

public class MessagesAggregate {

    @AggregateIdentifier
    private String id;

    @CommandHandler
    public MessagesAggregate(CreateMessageCommand command) {
        apply(new MessageCreatedEvent(command.getId(), command.getText()));
    }

    @EventHandler
    public void on(MessageCreatedEvent event) {
        this.id = event.getId();
    }

    @CommandHandler
    public void markRead(MarkReadMessageCommand command) {
        apply(new MessageReadEvent(id));
    }

   //standard constructors
}

各集約は

id

フィールドを持つ必要があります、そしてそれを

AggregateIdentifier

アノテーションを使って指定します。


CreateMessageCommand

が到着したときに集約が作成されます – そのコマンドを受け取ると

MessageCreatedEvent

が生成されます。

この時点で、集約は

messageCreated

状態にあります。

MarkReadMessageCommand

が到着すると、集約は__MessageReadEventを生成します。


6. セットアップをテストする

まず、

MessagesAggregateの


FixtureConfiguration

__を作成してテストを設定する必要があります。

private FixtureConfiguration<MessagesAggregate> fixture;

@Before
public void setUp() throws Exception {
    fixture =
      new AggregateTestFixture<MessagesAggregate>(MessagesAggregate.class);
}

最初のテストケースは最も単純な状況をカバーする必要があります –

CreateMessageCommand

が集合体に到着すると、__MessageCreatedEventが生成されます。

String eventText = "Hello, how is your day?";
String id = UUID.randomUUID().toString();
fixture.given()
  .when(new CreateMessageCommand(id, eventText))
  .expectEvents(new MessageCreatedEvent(id, eventText));

次に、集計関数が既に生成された

MessageCreatedEvent



MarkReadMessageCommand

が到着したときの状況をテストします。 __MessageReadEventが生成されます。

String id = UUID.randomUUID().toString();

fixture.given(new MessageCreatedEvent(id, "Hello"))
  .when(new MarkReadMessageCommand(id))
  .expectEvents(new MessageReadEvent(id));


7. すべてをまとめる

コマンド、イベント、および集計を作成しました。私たちのアプリケーションを起動するには、すべてを一緒に接着する必要があります。

まず、コマンドが送信されるコマンドバスを作成する必要があります。

CommandBus commandBus = new SimpleCommandBus();
CommandGateway commandGateway = new DefaultCommandGateway(commandBus);

次に、生成されたイベントが送信されるメッセージバスを設定する必要があります。

EventStore eventStore = new EmbeddedEventStore(new InMemoryEventStorageEngine());

EventSourcingRepository<MessagesAggregate> repository
  = new EventSourcingRepository<>(MessagesAggregate.class, eventStore);

イベントは永続的であるべきなので、それらを格納するためのリポジトリを定義する必要があります。

この簡単な例では、イベントをメモリに保存しています。プロダクションシステムでは、もちろんデータベースか他のタイプの永続ストアであるべきです。

イベントソーシングを行っている場合、

EventStore

がアーキテクチャの中心的なコンポーネントです。集約によって生成されたすべてのイベントは、システム内のすべての変更のマスターレコードを保持するために保管するために永続化する必要があります。

イベントは不変なので、一度イベントストアに保存されると、変更したり削除したりすることはできません。イベントを使用して、特定の時点までに生成されたすべてのイベントを取得して、その時点の任意の時点までシステムの状態を再現できます。

アプリケーションを起動する前に、コマンドを処理しイベントを生成するアグリゲートを設定する必要があります。

AggregateAnnotationCommandHandler<MessagesAggregate> handler =
  new AggregateAnnotationCommandHandler<MessagesAggregate>(
    MessagesAggregate.class, repository);
handler.subscribe(commandBus);

  • 最後に宣言する必要があるのは、** 集約によって生成されたイベントをサブスクライブするハンドラです。


MessageCreatedEvent

と__MessageReadEventの両方のメッセージを処理するメッセージイベントハンドラを作成します。

public class MessagesEventHandler {

    @EventHandler
    public void handle(MessageCreatedEvent event) {
        System.out.println("Message received: " + event.getText() + " (" + event.getId() + ")");
    }

    @EventHandler
    public void handle(MessageReadEvent event) {
        System.out.println("Message read: " + event.getId());
    }
}


subscribe()

メソッドを呼び出して、このリスナーがイベントを処理するように定義する必要があります。

AnnotationEventListenerAdapter annotationEventListenerAdapter
  = new AnnotationEventListenerAdapter(new MessagesEventHandler());
eventStore.subscribe(eventMessages -> eventMessages.forEach(e -> {
    try {
        annotationEventListenerAdapter.handle(e);
    } catch (Exception e1) {
        throw new RuntimeException(e1);
    }
}));

  • これでセットアップは完了したので、__commandGatewayにコマンドを送信できます。

String itemId = UUID.randomUUID().toString();
commandGateway.send(new CreateMessageCommand(itemId, "Hello, how is your day?"));
commandGateway.send(new MarkReadMessageCommand(itemId));

アプリケーションを実行した後、

MessagesEventHandler



MessagesAggregate

クラスによって生成されたイベントを処理するはずで、同様の出力が表示されます。

Message received: Hello, how is your day? (d2ba9cbe-1a44-428e-a710-13b1bdc67c4b)
Message read: d2ba9cbe-1a44-428e-a710-13b1bdc67c4b


8結論

この記事では、CQRSとEvent Sourcingシステム・アーキテクチャーを構築するための強力な基盤としてAxonフレームワークを紹介しました。

このフレームワークを使用して単純なメッセージアプリケーションを実装しました。これを実際にどのように構成する必要があるかを示します。

これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/axon[over on GitHub]にあります。これはMavenプロジェクトなので、そのままインポートして実行するのは簡単なはずです。