Axon FrameworkでイベントデータをJSONでSerializeする [Kotlin,SpringBoot]

## はじめに

Axon Framework（以降、Axonと記述）では、イベントデータを永続化する際、デフォルトではXMLにSerializeされます。

XMLの代わりにJSONでSerializeする方法を確認しました。

なお、確認する際に使用したアプリケーションについては、以下の記事でセットアップしたものを使用しているので、詳細はそちらをご参照ください

https://qiita.com/ynstkt/items/571d05b53eac493251f2

## デフォルトの状態

デフォルトでは、`domain_event_entry`テーブルの`meta_data`と`payload`のカラムには、XMLでSerializeされた値が保存されました。


```xml:meta_data
<meta-data>
  <entry>
    <string>traceId</string>
    <string>3ca00886-597e-455d-b0cf-172a288a9e1f</string>
  </entry>
  <entry>
    <string>correlationId</string>
    <string>3ca00886-597e-455d-b0cf-172a288a9e1f</string>
  </entry>
</meta-data>
```

```xml:payload
<org.example.axonkafkatrial.producer.service.DocCreated>
  <docId>cb7234ff-e787-40f7-acdf-163f7630b20f</docId>
  <body>test</body>
</org.example.axonkafkatrial.producer.service.DocCreated>
```

また、Kafkaにイベントデータを送信する場合、Kafkaメッセージのvalueも上記の`payload`と同じXMLとなっていました。


## JSONでSerializeするように変更

application.ymlにSerializerの設定を追加します。

```yaml:src/main/resources/application.yml
axon:
  axonserver:
    enabled: false
  kafka:
    bootstrap-servers: localhost:9094
  # 以下を追加
  serializer:
    events: jackson
```

なお、JSONへの変換にはjacksonを使用しますが、jacksonについては、上述のセットアップの記事内で、APIリクエストのJSONパースのために、以下の依存を追加済の状態です。


```kotlin:build.gradle.kts
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.1")
```

これで、`domain_event_entry`テーブルの`meta_data`と`payload`のカラムの値が、以下のようなJSONになりました。

```json:meta_data
{
  "traceId": "6baee93e-80c1-414b-8f7e-92c0f4744cb4",
  "correlationId": "6baee93e-80c1-414b-8f7e-92c0f4744cb4"
}
```

```json:payload!
{
  "docId": "020081d9-8915-4608-8749-6f8d7bd1eb2e",
  "body": "test"
}
```

Kafkaに送信されたメッセージのvalueもJSONになりました。

![serialize-to-json.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123840/8ce27b80-fe93-445b-7981-ea9934a61240.png)


## Serializerの適用範囲

Axonのドキュメントの以下を見ると、serilalizerで設定する項目として、`general`、`events`、`messages`の3種類があるようです

https://docs.axoniq.io/axon-framework-reference/4.10/serialization/#_serializer_implementations

```yaml
# Possible values for these keys are `default`, `xstream`, `java`, and `jackson`.
axon:
    serializer:
        general:
        events:
        messages:
```

試しに、以下のようにapplication.ymlの設定を変えてみました。

```yaml:src/main/resources/application.yml
axon:
  axonserver:
    enabled: false
  kafka:
    bootstrap-servers: localhost:9094
  serializer:
#    events: jackson
    general: jackson # eventsでなくgeneralに変えてみる
```

```yaml:src/main/resources/application.yml
axon:
  axonserver:
    enabled: false
  kafka:
    bootstrap-servers: localhost:9094
  serializer:
#    events: jackson
#    general: jackson
    messages: jackson # messagesに変えてみる
```

結果として、どの設定でも、`domain_event_entry`テーブルおよびKafkaメッセージの値は、同様にJSONになりました。

そこで、改めてドキュメントをよく読んでみます。

> Axon uses the `XStreamSerializer` by default, which uses [XStream](http://x-stream.github.io/) to serialize into and deserialize from XML.
> 

デフォルトだと`XStreamSerializer`が使われてXMLになるのは、確認したとおりです。

> Additionally, Axon provides the `JacksonSerializer`. This `Serializer` implementation uses [Jackson](https://github.com/FasterXML/jackson) to serialize objects into and deserialize from JSON.
> 

`JacksonSerializer`を指定すればJSONになります。これも確認したとおりです。

> You may also implement your own serializer simply by creating a class that implements `Serializer`and setting it within Axon’s configuration for the desired infrastructure components.
> 

また、独自のSerializerも作成可能なようなので、例えばKafka連携するうえでAvroを使いたい等であれば、自分で実装することになりそうです。

> - The Event `Serializer` is in charge of (de)serializing event message payload and metadata. Events are typically stored in an event store for a long period of time. This is the main driver for choosing the event serializer implementation.
> - The Message `Serializer` is in charge of (de)serializing the command and query message payload and metadata (used in a distributed application setup). Messages are shared between nodes and typically need to be interoperable and/or compact. Take this into account when choosing the message serializer implementation.
> - The default `Serializer` is in charge of (de)serializing the remainder, being the messages (except the payload and metadata), tokens, snapshots, deadlines and sagas. These objects are generally not shared between different applications, and most of these classes aren’t expected to have some of the getters and setters that are, for example, typically required by Jackson based serializers. For example, a `QueryMessage` consists of a payload and `ResponseType`, which will respectively be (de)serialized using the `message` and the `default` serializer, the query request and response payload will be (de)serialized using the`message`serializer. A flexible, general-purpose serializer like [XStream](http://x-stream.github.io/) is ideal for this purpose.

Serializerには、Event用、Message用、default用の3種類あり、それぞれのSerializerは以下で使用されるようです。

- Event用：イベントデータのmeta_dataとpayload
- Message用：commandとquery
- default用：その他のmessageや、token、snapshots、deadlines、sagas

Spring Bootのappication.ymlに設定する値とは微妙にキーワード表記が異なりますが、以下の対応関係になると推測します。

- events：Event用
- messages：Message用
- general：default用

> There is an implicit ordering between the configurable serializer. If no event `Serializer` is configured, the event de-/serialization will be performed by the message `Serializer`. In turn, if no message `Serializer` is configured, the default `Serializer` will take that role.
> 

Serializerの利用には優先順位があるようです。
イベントデータのmeta_dataとpayloadは

1. `events: jackson`ならJSONになる
2. `events`の設定がなくても`messages: jackson`ならJSONになる
3. `messages`の設定がなくても`general: jackson`ならJSONになる

ということなのかと思います。

今回の目的である、イベントデータをJSONでSerializeするためだけであれば、`events: jackson`を設定すればよい、ということになりそうです。

他のデータもJSONにしたい、データごとにSerialize方法を使い分けたい、という場合には、general、messagesに変更したり併用することになると思われます。

## まとめ

Spring Bootを使用している場合は、application.ymlで`axon.serializer`の設定を行うことでJSONでSerializeするように変更できました。

`axon.serializer`の設定においては、指定したSerializerを適用したいデータの種類に応じて、`events`、`messages`、`general`の3種類を使い分けたり併用する必要があることがわかりました。