【问题标题】:Event class changes with Axon CQRS事件类随 Axon CQRS 变化
【发布时间】:2019-12-05 09:06:15
【问题描述】:

在 axon 中测试事件迁移时,我发现具有默认值的新字段实际上设置为 null。 将字段(具有默认值)实际添加到先前事件的好方法是什么? 我不想有一个 MyEventV2 值是强制性的(没有默认值)并且不想指定一个“upcaster”。

首先,我们有这个事件,它被发布并存储在轴突服务器中:

data class MyEvent(
    val someId: String,
    val name: String
)

稍后我们更改 MyEvent,我们给了一个默认值以保持与以前事件的兼容性持久化,我们希望旧事件具有此默认值。 真实结果:'newField'在接收旧事件时为null。

data class MyEvent(
    val someId: String,
    val name: String,
    val newField: String = "defaultValueButWillBeNullInPreviousEvents... -> would need it to have the actual default value"
)

我使用 axon server、axon 4.2、spring boot 2.2.0 进行了测试,所有默认值都来自 axon boot starter,Jackson 2.10.0

我们是否应该使用类似的东西(但根本不是很好......):

data class MyEvent(
    val someId: String,
    val name: String,
    private val newFieldV2: String? = null
) {
    val newField: String
        get() = newFieldV2 ?: "my default value"
}

我还没有检查过 axon 如何从事件存储中重建事件,但我猜它是通过字段访问(反射)而不是构造函数来完成的。 但是很奇怪,因为数据类没有提供默认的空构造函数...

编辑: Axon 默认使用 XStreamSerializer,我已将配置切换为使用 Jackson,但问题仍然存在,并且注册了 Jackson kotlin 模块。

编辑2: 更新到 Jackson 2.10.1(请参阅 issue)应该可以修复,但由于某种原因,我收到另一个错误“com.fasterxml.jackson.databind.exc.InvalidDefinitionException:无法构造 org.x.x.MyEvent 的实例(没有创作者,如默认构造, 存在):无法从 Object 值反序列化(没有基于委托或属性的 Creator)”。

如果添加“ParameterNamesModule”会出现错误,如果没有添加,则事件的默认值仍然为空。

编辑3: 我写了测试,但是 axon 仍然有事件 org.axonframework.serialization.json.JacksonSerializer 的错误

@ExperimentalStdlibApi
class JsonDefaultValueTest {

    private lateinit var mapper1: ObjectMapper
    private lateinit var mapper2: ObjectMapper
    private lateinit var mapper3: ObjectMapper
    private lateinit var mapper4: ObjectMapper
    private lateinit var mapper5: ObjectMapper
    private lateinit var mapper6: ObjectMapper

    @BeforeEach
    fun setup() {
        mapper1 = Jackson2ObjectMapperBuilder.json()
            .build()
        mapper2 = ObjectMapper()
        mapper3 = ObjectMapper().registerModule(KotlinModule())
        mapper4 = ObjectMapper().registerModule(KotlinModule(nullisSameAsDefault = true))
        mapper5 = ObjectMapper().registerModule(ParameterNamesModule())
        mapper6 = ObjectMapper()
            .registerModule(ParameterNamesModule())
            .registerModule(KotlinModule())
            .registerModule(Jdk8Module())
            .registerModule(JavaTimeModule())
    }

    @Test
    fun `only s1 passed with mapper 1`() {
        val json = """{"s1":"only s1"}"""

        val event = mapper1.readValue<MyEvent>(json)

        val event2 = mapper1.readerFor(MyEvent::class.java).readValue<MyEvent>(json.encodeToByteArray())

        val expected = MyEvent(
            "only s1",
            aInt = 0
        )

        Assertions.assertEquals(expected, event)
    }

    @Test
    fun `only s1 passed with mapper 2`() {
        val json = """{"s1":"only s1"}"""

        assertThrows<InvalidDefinitionException> { mapper2.readValue<MyEvent>(json) }

        assertThrows<InvalidDefinitionException> { mapper2.readerFor(MyEvent::class.java).readValue<MyEvent>(json.encodeToByteArray()) }

    }

    @Test
    fun `only s1 passed with mapper 3`() {
        val json = """{"s1":"only s1"}"""

        val event = mapper3.readValue<MyEvent>(json)

        val event2 = mapper3.readerFor(MyEvent::class.java).readValue<MyEvent>(json.encodeToByteArray())

        val expected = MyEvent(
            "only s1",
            aInt = 0
        )

        Assertions.assertEquals(expected, event)
        Assertions.assertEquals(expected, event2)
    }

    @Test
    fun `only s1 passed with mapper 4`() {
        val json = """{"s1":"only s1"}"""

        val event = mapper4.readValue<MyEvent>(json)

        val event2 = mapper4.readerFor(MyEvent::class.java).readValue<MyEvent>(json.encodeToByteArray())

        val expected = MyEvent(
            "only s1",
            aInt = 0
        )

        Assertions.assertEquals(expected, event)
        Assertions.assertEquals(expected, event2)
    }

    @Test
    fun `only s1 passed with mapper 5`() {
        val json = """{"s1":"only s1","s2":null}"""

        assertThrows<ValueInstantiationException> { mapper5.readValue<MyEvent>(json) }

        assertThrows<ValueInstantiationException> { mapper5.readerFor(MyEvent::class.java).readValue<MyEvent>(json.encodeToByteArray()) }

    }

    @Test
    fun `only s1 passed with mapper 6`() {
        val json = """{"s1":"only s1"}"""

        val event = mapper6.readValue<MyEvent>(json)

        val event2 = mapper6.readerFor(MyEvent::class.java).readValue<MyEvent>(json.encodeToByteArray())

        val expected = MyEvent(
            "only s1",
            aInt = 0
        )

        Assertions.assertEquals(expected, event)
        Assertions.assertEquals(expected, event2)
    }

    data class MyEvent(
        val s1: String,
        val aInt: Int,
        val s2: String = "my default"
    )
}

【问题讨论】:

    标签: java spring-boot kotlin cqrs axon


    【解决方案1】:

    这似乎是 Jackson-Kotlin 问题。您需要明确配置 Jackson 以将“null”(或缺少字段)视为“使用默认值”。

    按照https://github.com/FasterXML/jackson-module-kotlin/issues/130#issuecomment-546625625 中的建议,您应该使用:

    ObjectMapper().registerModule(KotlinModule(nullisSameAsDefault = true)

    【讨论】:

    • 我在这个问题之前看到了,由于某种原因我没有在源代码中看到nullisSameAsDefault,我再次检查并这次看到了。现在我收到此错误:com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 无法构造org.x.MyEvent 的实例(没有创建者,如默认构造,存在):无法从对象值反序列化(没有基于委托或属性的创建者) 然后根据我通过 Jackson2ObjectMapperBuilderCustomizer 自定义的模块,我没有收到此错误,但会获得默认字段的空值(即使使用 KotlinModule(nullisSameAsDefault = true))
    • 通过我的编辑 3,我们看到 jackson 可以毫无问题地使用 kotlin 默认值。我应该在 axon github 中创建一个问题吗? Axon 的 JacksonSerializer 似乎没有按预期工作。 (我确实配置了一个 bean ObjectMapper,甚至尝试创建自己的 JacksonSerializer)
    【解决方案2】:

    这听起来像是用于反序列化事件的 Jackson ObjectMapper 配置不正确。为了让它与 Kotlin 特定的功能(例如默认值)一起使用,您需要注册 Kotlin 模块:https://github.com/FasterXML/jackson-module-kotlin

    val mapper = ObjectMapper().registerModule(KotlinModule())
    

    【讨论】:

    • 我的maven依赖中有jackson-module-kotlin,所以应该使用spring boot自动注册。
    • 这可能是 Axon 默认使用 XStreamSerializer 但不确定的问题
    • axon 中的默认值是 XStream,我切换到 Jackson 处理事件,但对于具有默认值的字段仍然为 null。在调试模式下,我发现 EventBuffer 具有 jackson 序列化器字段,其中注册的模块是:com.fasterxml.jackson.datatype.jdk8.Jdk8Module、com.fasterxml.jackson.datatype.jsr310.JavaTimeModule、com.fasterxml.jackson .module.kotlin.KotlinModule, com.fasterxml.jackson.module.paramnames.ParameterNamesModule, org.springframework.boot.jackson.JsonComponentModule docs.axoniq.io/reference-guide/operations-guide/…
    猜你喜欢
    • 2021-02-23
    • 1970-01-01
    • 2020-05-18
    • 1970-01-01
    • 1970-01-01
    • 2017-06-29
    • 1970-01-01
    • 2021-12-19
    • 2013-01-10
    相关资源
    最近更新 更多