JpaSagaStore в сочетании с Jackson не может правильно хранить состояние

В приложении SpringBoot у меня следующая конфигурация:

axon:
  axonserver:
    servers: "${AXON_SERVER:localhost}"
  serializer:
    general: jackson
    messages: jackson
    events: jackson

logging.level:
  org.axonframework.modelling.saga: debug

Уменьшение размера сценария до минимума, соответствующая часть класса Saga:

@Slf4j
@Saga
@ProcessingGroup("AuctionEventManager")
public class AuctionEventManagerSaga {
    @Autowired
    private transient EventScheduler eventScheduler;

    private ScheduleToken scheduleToken;
    private Instant auctionTimerStart;

    @StartSaga
    @SagaEventHandler(associationProperty = "auctionEventId")
    protected void on(final AuctionEventScheduled event) {
        this.auctionTimerStart = event.getTimerStart();

        // Cancel any pre-existing previous job, since the scheduling thread might be lost upon a crash/restart of JVM.
        if (this.scheduleToken != null) {
            this.eventScheduler.cancelSchedule(this.scheduleToken);
        }

        this.scheduleToken = this.eventScheduler.schedule(
            this.auctionTimerStart,
            AuctionEventStarted.builder()
                .auctionEventId(event.getAuctionEventId())
                .build()
        );
    }

    @EndSaga
    @SagaEventHandler(associationProperty = "auctionEventId")
    protected void on(final AuctionEventStarted event) {
        log.info(
            "[AuctionEventManagerSaga] Current state: {scheduleToken={}, auctionTimerStart={}}",
            this.scheduleToken,
            this.auctionTimerStart
        );
    }
}

В конечном скомпилированном классе у нас будет 4 свойства: log (из @Slf4j), eventScheduler (переходный, @Autowired), scheduleToken и auctionTimerStart.

Для справочной информации вот пример общего подхода, который я использовал для классов Command и Event:

@Value
@Builder
@JsonDeserialize(builder = AuctionEventStarted.AuctionEventStartedBuilder.class)
public class AuctionEventStarted {
    AuctionEventId auctionEventId;

    @JsonPOJOBuilder(withPrefix = "")
    public static final class AuctionEventStartedBuilder {}
}

При выполнении кода вы получите следующий результат:

2020-05-12 15:40:01.180 DEBUG 1 --- [mandProcessor-4] o.a.m.saga.repository.jpa.JpaSagaStore   : Updating saga id c8aff7f7-d47f-4616-8a96-a40044cb7e3b as {}

Как только общий сериализатор изменяется на xstream, содержимое сериализуется правильно, но я сталкиваюсь с другой проблемой во время десериализации, поскольку у меня есть классы private static final class Builder, использующие Lombok.

Итак, есть ли у Axon способ справиться с этими сценариями:

1- Axon для безопасного управления Джексоном, чтобы игнорировать @Autowired, переходные и статические свойства из @Saga классов? Я попытался вручную определить @JsonIgnore в негосударственных свойствах, но это все равно не сработало.

2- Axon для безопасной настройки XStream для игнорирования внутренних классов (в основном классов Builder, реализованных как частный статический финал)?

Заранее спасибо,


РЕДАКТИРОВАТЬ: Я ищу решение, используя предпочитаемый мной сериализатор: JSON. Я попытался изменить класс саги и расширить JsonSerializer<AuctionEventManagerSaga>. Для этого я реализовал методы:

    @Override
    public Class<AuctionEventManagerSaga> handledType() {
        return AuctionEventManagerSaga.class;
    }

    @Override
    public void serialize(
        final AuctionEventManagerSaga value,
        final JsonGenerator gen,
        final SerializerProvider serializers
    ) throws IOException {
        gen.writeStartObject();
        gen.writeObjectField("scheduleToken", value.eventScheduler);
        gen.writeObjectField("auctionTimerStart", value.auctionTimerStart);
        gen.writeEndObject();
    }

Прямо сейчас у меня что-то сериализуется, но это не имеет ничего общего с определенными мною свойствами:

2020-05-12 16:20:01.322 DEBUG 1 --- [mandProcessor-0] o.a.m.saga.repository.jpa.JpaSagaStore   : Storing saga id c4b5d94c-7251-40a5-accf-332768b1cacd as {"delegatee":null,"unwrappingSerializer":false}

РЕДАКТИРОВАТЬ 2. Решил подробнее рассказать о проблеме, с которой я сталкиваюсь, когда переключаю общий режим на использование XStream (хотя это несколько не связано с основной проблемой, описанной в заголовке).

Вот проблема, на которую он мне жалуется:

2020-05-12 17:08:06.495 DEBUG 1 --- [ault-executor-0] o.a.a.c.command.AxonServerCommandBus     : Received command response [message_identifier: "79631ffb-9a87-4224-bed3-a957730dced7"
error_code: "AXONIQ-4002"
error_message {
  message: "No converter available\n---- Debugging information ----\nmessage             : No converter available\ntype                : jdk.internal.misc.InnocuousThread\nconverter           : com.thoughtworks.xstream.converters.reflection.ReflectionConverter\nmessage[1]          : Unable to make field private static final jdk.internal.misc.Unsafe jdk.internal.misc.InnocuousThread.UNSAFE accessible: module java.base does not \"opens jdk.internal.misc\" to unnamed module @7728643a\n-------------------------------"
  location: "1@600b5b87a922"
  details: "No converter available\n---- Debugging information ----\nmessage             : No converter available\ntype                : jdk.internal.misc.InnocuousThread\nconverter           : com.thoughtworks.xstream.converters.reflection.ReflectionConverter\nmessage[1]          : Unable to make field private static final jdk.internal.misc.Unsafe jdk.internal.misc.InnocuousThread.UNSAFE accessible: module java.base does not \"opens jdk.internal.misc\" to unnamed module @7728643a\n-------------------------------"
}
request_identifier: "2f7020b1-f655-4649-bbe0-d6f458b3c2f3"
]
2020-05-12 17:08:06.505  WARN 1 --- [ault-executor-0] o.a.c.gateway.DefaultCommandGateway      : Command 'ACommandClassDispatchedFromSaga' resulted in org.axonframework.commandhandling.CommandExecutionException(No converter available
---- Debugging information ----
message             : No converter available
type                : jdk.internal.misc.InnocuousThread
converter           : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
message[1]          : Unable to make field private static final jdk.internal.misc.Unsafe jdk.internal.misc.InnocuousThread.UNSAFE accessible: module java.base does not "opens jdk.internal.misc" to unnamed module @7728643a
-------------------------------)

Все еще не повезло с решением этой проблемы ...


person Guilherme Blanco    schedule 12.05.2020    source источник


Ответы (2)


Я работал над системами Axon, где Serializer использовалась только реализация JacksonSerializer. Имейте в виду, что это не то, что рекомендует команда Axon. Для сообщений (то есть команд, событий и запросов) имеет смысл использовать JSON в качестве сериализованного формата. Но переключение общего Serializer на jackson означает, что вы должны засорять логику своей предметной области (например, вашу сагу) спецификой Джексона, «чтобы она работала».

Тем не менее, возвращаюсь к моему успешному варианту использования сериализованных саг о Джексоне. В этом случае мы использовали правильное соответствие аннотаций JSON в полях, которые мы хотели учитывать (фактическое состояние), и игнорировать ту, которую мы не хотели десериализовать (с transient или @JsonIgnore). Почему оба варианта не работают в вашем сценарии, на данном этапе не совсем понятно.

Что я действительно помню, так это то, что команда упомянутого проекта очень четко решила против Lombok из-за «общих странностей», когда дело доходит до де- / сериализации. Таким образом, в качестве пробной версии, возможно, стоит не использовать любые аннотации / логику Lombok в классе Saga и посмотреть, сможете ли вы правильно де- / сериализовать их в таком состоянии.

Если в тот момент это действительно сработает, я думаю, вы нашли виновника ныряния в дальнейших поисках.

Я знаю, что это не точный ответ, но надеюсь, что он вам все равно поможет! Возможно, стоит поделиться репозиторием, в котором возникают эти проблемы; может прояснить проблему и для других.

person Steven    schedule 13.05.2020
comment
В итоге я нашел проблему в XStream. Было одно поле, которое не было transient, и теперь сериализация XStream работала как шарм. Использование смешанного сценария JSON для сообщений и событий и XStream в целом было бы сложной задачей, поскольку для правильного поведения событий потребовались бы как аннотации Jackson, так и XStream. На данный момент я переключил все на XStream, поскольку компания, в которой я работаю, использует только Java, и было бы нормально декодировать события с помощью XStream. Не самое лучшее решение, поскольку я действительно хотел (и надеялся), что JSON будет поддерживаться должным образом из коробки. - person Guilherme Blanco; 14.05.2020
comment
Если вы используете Jackson для событий / сообщений и XStream для остальных, тогда не должно быть необходимости иметь обе аннотации для событий, поскольку события не будут сериализованы XStream в таком сценарии. Я смешал оба сериализатора в большом количестве проекта, не требуя специальной поддержки XStream. Не могли бы вы пояснить, почему это необходимо? - person Steven; 15.05.2020

Мне удалось решить проблему №2 при использовании XStream в качестве общего сериализатора. В одной из саг было @Autowired свойство зависимости, которое не было transient. XStream выдавал какое-то загадочное сообщение, но нам удалось отследить проблему и решить ее.

Что касается поддержки JSON, нам не повезло. В итоге мы переключили все на XStream, поскольку компания использует только Java, и было бы нормально декодировать события с помощью XStream.

Не самое лучшее решение, поскольку мы действительно хотели (и надеялись), что JSON будет поддерживаться должным образом из коробки. Имейте в виду, что это связано с использованием Lombok, которое в данном случае вызывало неудобства.

person Guilherme Blanco    schedule 14.05.2020
comment
То, что вы не смогли использовать сочетание XStreamSerializer и JacksonSerializer, довольно забавно, если вы спросите меня. Я работал и видел много проектов, в которых события / сообщения использовали JacksonSerializer, а значение XStream по умолчанию использовалось для Saga's, Tokens и Snapshots. Мне было бы любопытно понять, почему вы не смогли достичь этого соединения @Guilherme, поскольку теперь кажется, что Axon не хватает функциональности, которую, как я знаю, он включает. - person Steven; 18.05.2020
comment
Возможно, я забыл быть более точным ... смешивание XStream с Jackson в сочетании с Lombok. - person Guilherme Blanco; 19.05.2020
comment
Спасибо, что разъяснили, что Гильерме. Это ценная информация для других людей, которые могут столкнуться с этой вашей проблемой :-) - person Steven; 20.05.2020