johanhaleby / occurrent

Unintrusive Event Sourcing Library for the JVM
https://occurrent.org
120 stars 16 forks source link

JacksonCloudEventConverter doesn't use CloudEventTypeMapper when converting from CloudEvents to Domain Events #119

Closed Keisuki closed 2 years ago

Keisuki commented 2 years ago

Hi guys. This appears to be a bug, but if I'm just using it wrong, feel free to tell me to go away :smile:

When converting a domain event to a CloudEvent, JacksonCloudEventConverter correctly uses its cloudEventTypeMapper to decide what event type string to include in the event. This allows the user to inject their own type mapper, decoupling the event type string from the fully qualified Java class name.

    @Override
    public CloudEvent toCloudEvent(T domainEvent) {
        requireNonNull(domainEvent, "Domain event cannot be null");
        // @formatter:off
        PojoCloudEventData<Map<String, Object>> cloudEventData = PojoCloudEventData.wrap(objectMapper.convertValue(domainEvent, new TypeReference<Map<String, Object>>() {}), objectMapper::writeValueAsBytes);
        // @formatter:on
        return CloudEventBuilder.v1()
                .withId(idMapper.apply(domainEvent))
                .withSource(cloudEventSource)
                .withType(cloudEventTypeMapper.getCloudEventType(domainEvent))
                .withTime(timeMapper.apply(domainEvent))
                .withSubject(subjectMapper.apply(domainEvent))
                .withDataContentType(contentType)
                .withData(cloudEventData)
                .build();
    }

However, when converting back to a domain event from a CloudEvent, it appears to ignore the cloudEventTypeMapper entirely and determine the class to deserialize to based on the type string as a fully qualified Java class name

    @Override
    public T toDomainEvent(CloudEvent cloudEvent) {
        CloudEventData data = cloudEvent.getData();

        final Class<T> domainEventType;
        try {
            domainEventType = (Class<T>) Class.forName(cloudEvent.getType());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

Here's an example of code which exhibits this bug:

import com.fasterxml.jackson.databind.ObjectMapper
import org.occurrent.application.converter.jackson.JacksonCloudEventConverter
import org.occurrent.application.converter.typemapper.CloudEventTypeMapper
import java.net.URI

sealed interface BaseEvent;

data class MyEvent(val someField: String) : BaseEvent

class MyCloudEventTypeMapper : CloudEventTypeMapper<BaseEvent> {
    override fun getCloudEventType(type: Class<out BaseEvent>): String {
        return "MyEventType"
    }

    override fun <E : BaseEvent> getDomainEventType(cloudEventType: String): Class<E> {
        return MyEvent::class.java as Class<E>
    }
}

fun main() {
    val objectMapper = ObjectMapper()
    val cloudEventConverter: JacksonCloudEventConverter<BaseEvent> = JacksonCloudEventConverter.Builder<BaseEvent>(objectMapper, URI.create("urn:myevents"))
        .typeMapper(MyCloudEventTypeMapper())
        .build()

    val data = cloudEventConverter.toCloudEvent(MyEvent("123"))

    println(data)
    println(cloudEventConverter.toDomainEvent(data))
}

This outputs the following:

CloudEvent{id='f78a6f70-7150-4dfd-bc27-15e3a732022d', source=urn:myevents, type='MyEventType', datacontenttype='application/json', time=2022-07-29T09:48:53.589568100Z, data=io.cloudevents.core.data.PojoCloudEventData@dbcc0a73, extensions={}}
Exception in thread "main" java.lang.RuntimeException: java.lang.ClassNotFoundException: MyEventType

Using a debugger, we can see that getDomainEventType on MyCloudEventTypeMapper is never invoked.

johanhaleby commented 2 years ago

Hi! Thanks for using Occurrent and thanks for reporting this issue. I'll look into it :)

johanhaleby commented 2 years ago

I've fixed it now! Will make a new release.

Keisuki commented 2 years ago

Fantastic! That was really quick

johanhaleby commented 2 years ago

I've released version 0.14.5 which should address the issue :) Thanks again for reporting!