Skip to content

[#3250] feat: Persistent Streams aligned with Axon Framework 5 API#4039

Draft
MateuszNaKodach wants to merge 10 commits intomainfrom
feat/3520_af5_persistent_streams
Draft

[#3250] feat: Persistent Streams aligned with Axon Framework 5 API#4039
MateuszNaKodach wants to merge 10 commits intomainfrom
feat/3520_af5_persistent_streams

Conversation

@MateuszNaKodach
Copy link
Copy Markdown
Contributor

@MateuszNaKodach MateuszNaKodach commented Dec 18, 2025

This project shows current progress and may be the starting point for futher development.

What I have done:

  • expanded interface SubscribableEventSource to support filtering by EventCriteria

  • ported PersistentStreamConnection - with fix for reconnecting after intentional stream closed

  • new class PersistentStreamEventConverter - converting PersistentStreamEvents to MessageStream.Entry

  • ported PersistentStreamMessageSource - with inital support for filtering, but there is a problem with passing TrackingToken (check comment for this PR)

  • ported PersistentStreamEventSourceDefinition

  • ported PersistentStreamMessageSourceFactory

  • ported PersistentStreamScheduledExecutorBuilder

  • new class PersistentStreamSequencingPolicies - with a dictionary

  • AggregateEventConverter - new class that converts legacy Axon Server events to AF5

  • AxonServerConfiguration - restored PersistentStreamSettings - with default sequencing policy changed to SequentialPolicy

  • added tests, especially IntegrationTest for persistent streams

TODO:

  • solve problem with passing TrackingToken along with events
  • write configuration for persistent streams and Spring Boot integration

…ing connection with converter and filtering events by aggregate
…tStreamMessageSource` - wait for gRPC call to end
…ntional close

        Add closing flag to PersistentStreamConnection to distinguish between
        intentional close (user called close()) and unexpected disconnection
        (network error, server disconnect).

        When close() is called:
        - Set closing flag to true before closing the stream
        - streamClosed() callback checks flag and skips reconnection scheduling
        - messageAvailable() callback checks flag and skips task submission

        This prevents RejectedExecutionException when gRPC callbacks arrive
        after the scheduler has been shut down during intentional close.
…ersistentStreamMessageSource` - wait for gRPC call to end"

This reverts commit 3a41b2d.
…`PersistentStreamSettings` - SequentialPolicy as default
@MateuszNaKodach MateuszNaKodach self-assigned this Dec 18, 2025
@MateuszNaKodach MateuszNaKodach added Type: Feature Use to signal an issue is completely new to the project. Priority 1: Must Highest priority. A release cannot be made if this issue isn’t resolved. labels Dec 18, 2025
@MateuszNaKodach MateuszNaKodach added this to the Release 5.2.0 milestone Dec 18, 2025
if (!events.isEmpty()) {
// Pass null ProcessingContext - the SubscribingEventProcessor will create its own UnitOfWork.
// fixme: We lose TrackingToken so we cannot determine if it's replay or not!
FutureUtils.joinAndUnwrap(eventsBatchConsumer.apply(events, null));
Copy link
Copy Markdown
Contributor Author

@MateuszNaKodach MateuszNaKodach Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There we have a significant problem (maybe require even a change of the API). There is no way how to pass TrackingToken from Message.Entry Context to be procesessed along with the events. It'd be the best to have eventsBatchConsumer which accepts MessageStream.Entry(ies).
Now if we pass null, we cannot determine if it's a replay of not.

Before we had here EventWithToken, so the TrackingToken was passed to consumer along with each event. Thanks to the PersistentStreamEvent#isReplay we knew which one is a replay.

private void streamClosed(Throwable throwable) {
persistentStreamHolder.set(null);
if (throwable != null) {
if (throwable != null && !closing.get()) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be introduced to Axon Framework 4 as well.
We check if we ONLY reconnect if it's an exception, not intentional closing.

*/
@Override
@Nonnull
public MessageStream.Entry<EventMessage> apply(PersistentStreamEvent persistentStreamEvent) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In AF4 we were convertin to TrackedEventMessage - we don't have it anymore, so in order to preserve TrackingToken, we return here the entry so EventMessage along with Context.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
63.2% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority 1: Must Highest priority. A release cannot be made if this issue isn’t resolved. Type: Feature Use to signal an issue is completely new to the project.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants