Reactive for Axon Framework 5

I just finished watching the “State and future of AxonIQ” video. At around 55:50 mins @Steven_van_Beelen you mention in v5 you might change it so it is:

“using reactive APIs throughout the entirety of Axon Framework”

How far do you intend to go with this? I can imagine providing reactive gateways for example but will you be rewriting the whole framework to be reactive?! And would us non-reactive dinosaurs still be able to rely on the good old send and sendAndWait methods on the gateways?

I’m interested in understanding more and then maybe we can have this thread for comments from everyone once the potential change is better understood.

Personally, I think reactive at the edges (as a separate gateway which can be used or as the return type of a controller method [e.g. for a method returning the result of a subscription query]) makes sense. But otherwise I would like to avoid it. It adds too much baggage and forces you to into a specific style of programming which you cannot opt out of (and debugging hell).

I’ve enjoyed occasionally debugging and ending up in the Axon Framework code with my debugger - if the code was changed to be reactive it would be unapproachable to the vast majority of Java devs.

I would like the framework to go more the route of embracing Project Loom’s virtual threads.

Anyway I thought I would kick of the thread…

(Nice to see my name on the screen at 29:40 :slight_smile:

Awesome to hear you checked out the State and Future, @vab2048!

Concerning your request, we lean towards fully reactive APIs internally for the components where it makes sense.

The intent is to allow users to make their applications fully reactive.
Right now, Axon Framework is blocking that entirely, largely because of the ThreadLocal usage within the UnitOfWork.
With that said, this will thus cover all major infrastructure components.

We have been dabbling with the idea of Project Loom too.
However, we do not want to impose a non-LTS JDK version on our users.
As such, construction of Axon Framework using Project Loom throughout instead of, for example, Project Reactor isn’t feasible at this stage.

Note that we nowhere will force the user to make their applications reactive.
This should always be a choice instead of a requirement.
As such, gateways or edge layers will always provide a means for non-reactive access to the users.

I hope this sheds some light on our situation, @vab2048. Feel free to chip in with more insights on your end.
In all honesty, I am extremely happy with your reply.
You’re one of the first to argue whether we should take that route openly.

Thank you for opening this discussion, we were eager to find out what users think about that.
I understand your point, reactive style can be overwhelming compared to the traditional imperative coding style. But also, the nature of CQRS and Axon Framework is full of eventual consistency and asynchronous operations. CompletableFuture API is really bad and poor to support many complex cases that commonly occur, this is why we started looking into Reactive API. Also, there is no way to support streaming and backpressure with CompletableFutures, and nor will Project Loom help with that.

We are still discussing how we can solve some problems by using Project Reactor in our core without imposing a new programming style on “traditional” users while others still may utilize the full power of Reactive API.

We have been dabbling with the idea of Project Loom too.
However, we do not want to impose a non-LTS JDK version on our users.

This makes sense. Virtual threads are still only available as preview in Java 19 and so the first LTS release they will be a standard feature might not be until Java 21 (to be released Sept 2023). And if it skips Java 21 then the next LTS won’t be until 2025 (I think).

I understand your point, reactive style can be overwhelming compared to the traditional imperative coding style.

I am also worried about debugging. Currently even though I am not familiar with the code in Axon - it is at least approachable since it follows the traditional Java model. Switching over to reactive would make Axon more unapproachable when debugging and may limit the pool of open source contributions.

CompletableFuture API is really bad and poor to support many complex cases that commonly occur, this is why we started looking into Reactive API.

I agree the CompletableFuture API is a PITA. But with loom there will be no need to use it - you can just write blocking code and it will perform as good as non-blocking code.

Also, there is no way to support streaming and backpressure with CompletableFutures, and nor will Project Loom help with that.

I kind of agree and disagree. Since Loom will let you write blocking code and have it perform as good as non-blocking (or at least that is what we have been led to believe) you can use traditional Java constructs (making your code more readable and understandable than reactive). Quoting from a reddit post, you can use:

  • semaphores for limiting concurrency
  • for retries you can use loops (of course you can add some backoff logic and factor it out into a library)
  • exceptions for error handling
  • blocking for back-pressure

The one thing left out from there is the streaming… but I’m sure there will be a loom alternative at some point.

Just being able to write synchronous code and have it perform as good as async is a huge win. The integration with all the tooling will be there from the get go. Testing is simpler, etc.

Although reactive is impressive, it seems to be going against what the OpenJDK developers are touting as the future for Java. One (virtual) thread, one task. No need to worry about blocking because the Java runtime does the scheduling of the threads. ThreadLocals can be replaced by immutable scoped values.

The intent is to allow users to make their applications fully reactive. Right now, Axon Framework is blocking that entirely, largely because of the ThreadLocal usage within the UnitOfWork.

Thanks - this makes the requirement truly explicit. Maybe there is a way to refactor this specific section to support reactive without going reactive yourself (no idea if it is possible but just throwing thoughts here):

  • It may be possible to maybe make use of JEP 429 (it used to be called extent-local variables and now seems to be called scoped values) to avoid issues like this when using Loom. Obviously it is not yet available… which is a big blocker.
  • Instead of using static methods maybe the framework could inject a parameter into a @CommandHandler on which the apply method can be called.

I think going down the reactive route at a point in time when the OpenJDK developers themselves are saying you shouldn’t only really makes sense because of the time constraints (loom not being available and you need to solve problems now). When loom is released I am guessing the number of (new) projects using reactive will start to go down anyway :face_with_raised_eyebrow:.

Anyway these are just my initial thoughts. Hopefully others can chime in too.

By the way I am not totally against reactive. I actually like the fact that I can return a Flux in a controller for subscription queries. But the nice thing there is it is barely any reactive code :slight_smile:

4 Likes

Without giving my two cents on your response immediately, @vab2048, I want to thank you for taking the time to respond here.

It confirms some of the concerns I’ve been having.
And, it comes in a timely fashion, as we’re still flowing through the planning phases.

Furthermore, it speaks for you that you’re trying to help us and want to keep helping us in the future, which is plain awesome :heart:

We’ll be sure to keep this thread active.

2 Likes

I also watched the video yesterday and I had to search if there was already a discussion about reactive for Axon 5. I would definitely not be a fan of having reactive code style everywhere.
I mostly use it for controllers.
I have been following Project Loom for few years already and it is definitely the approach I would go for.
Extend local is feasible, if necessary, methods within aggregate/sagas could receive an object as a param in order to not depend on ThreadLocal or other.
Reactive code style adds complexity and debugging is quite a pain.
Also reactive code will change by using virtual threads so in the end (thread scheduling, etc), it will become a syntax code style and feature library with back-pressure, syntactic exception handling, retries, etc but all that, I do not see the advantage in command handling of aggregate/saga, and in event handling for projection or other.

I do not mind that you use reactive but please keep the option of using both approaches possible → use of loom (normal code style and debugging) and reactive.
And obviously I guess it should not be an issue to mix the usage within same project runtime.

There was a talk recently in Devoxx about loom, they demonstrated the difference between loom and reactive code style, and clearly the loom approach was much easier to understand, maintain.

1 Like

Thanks for the link to the vid @Yoann_CAPLAIN

Another one I found useful is: The Age of Virtual Threads by Ron Pressler And Alan Bateman - YouTube

In this one you can see the tooling in action - JFR, stack traces, debugging and it also mentions about thread dumps and best practices with loom.

Hi all,

first of all, thanks for sharing your concerns and ideas. This is exactly the reason why we decided to share our ideas up front. Unfortunately, whenever sharing ideas, we don’t always get to tell the whole story with all its context and considerations. What you saw in the presentation was merely a summary of our conclusions.

Let me share a few considerations here to give you some more background on what we’re planning on doing and why.

Working on Axon Framework for over 12 years has taught me a lot about API design. Throughout these 12 years, we’ve had four major releases where each time, we’ve changed to APIs to cope better with the types of applications that are built on top of it. More often than not, these are applications with demanding performance characteristics. Still, quite a few of the Axon Framework internals rely on the specific usage of threads. This is quite unfortunate in cases where message handler, for example, need to perform some asynchronous tasks.

Working with asynchronous tasks synchronously is a lot easier than the other way around. If that weren’t true, Loom would have been part of Java 9 :wink:. Therefore, all our internals need to be async-first. There will be multiple ways to deal with asynchronous code. We don’t want to make ourselves dependent on a project of OpenJDK that has been postponed (for good reasons) several times already. We also see that traditional async with CompletableFuture has severe limitations. That’s why we opted for Reactive.

But Axon, like any framework, is an onion. It has a lot of layers. In fact, we’ll be adding more layers. The plan is to make the core use Reactive, most likely project Reactor. We’ve already done some proofs of concept and found that the code will be a lot easier to digest.

Some of the layers around this core will still use the Reactive APIs. For example, we expect the Repository interface as well as the CommandBus and EventStore to return Flux or Mono. This way, we can make sure that people who wish to use this asynchronous approach still have the ability to do so. After all, for these components, location transparency is an essential ingredient.

But then come the outer layers of our onion. This is where things won’t actually change as much. For the annotated approach, we actually think we can be 100% backward compatible. You’ll be able to annotate a method and not worry about anything going on asynchronously in the framework.

But there will be subtle differences. For example, from these annotated methods, you can return a CompletableFuture, a Mono or Flux, or any other implementation of Publisher, for that matter, and Axon will be able to handle the asynchronous execution of your handler gracefully. Right now, whenever you’re able to return such a construct, the framework is forced to block and wait for a response.

But besides the annotated API, we’re also working on more declarative APIs to use Axon Framework. At their core, they will use a reactive style, but again, you can choose to use an extra layer of onions and keep it more procedural/traditional.

I hope this clarifies things a little bit better. Curious to find out what you think.
Cheers,

Allard

2 Likes

I might be a bit late to this discussion, but I just found this post and I wanted to share my opinion.

My team use Axon Framework rather extensively, and the lack of reactive support has been an unfortunate thorn in our side as we have a mainly reactive system.

I realize that project loom and virtual threads might upset this dynamic but the reality today is that reactive programming fills a very important function in the ecosystem.

My team and I are very excited by the potential reactive support in Axon 5, even though we seem to be in the minority in this thread.

My question is has this plan changed any since your last comment @allardbz? Now that project loom might be released in the next LTS compared to how it unsure it was in November 2022.

As Axon Framework plays such a central role in our systems we have held out for the potential release of at least a full version of the reactor extension, if Axon Framework 5 would include reactive support that would be even better for us. However, if the plans have changed, and reactive support is something that might not come, we will have to evaluate if migrating away from Reactive is the way to go.

1 Like

Hello Marcus,

thank you very much for your feedback. It is very valuable to have your point of view as well.

Nothing with respect to Axon 5 is set in stone at the moment. We are still conducting API experiments and are considering different strategies.

One thing that is certain is that we will be much more “async native” (for lack of a better term). Whether this means we’ll be using a Reactive API (such as Reactor or plain Reactive Streams), or that we will use more generic Java constructs such as completable future, is something that we are currently figuring out.

Our goal, however, is to be fully compatible with the concepts of Reactive Streams in our main APIs. For message handlers, this would mean you can return a Flux or Mono, for example, and Axon will nicely work with those (without blocking any threads).

In the meantime, we’re also taking Loom into account. It’s difficult to say how this should impact our APIs. It does make blocking operations as cheap as their non-blocking alternatives currently are. Our expectation is that this won’t primarily impact the framework, but allows users to perform operations like commandGateway.sendAndWait() as efficiently as commandGateway.send(...).thenApply() But project reactor, as an example of a Reactive Streams implementation, is much more than just asynchronous execution of code. Project Loom will lack any flow control mechanisms, for example.

In a few weeks, we have planned a few “heads down, lock the door, don’t answer the phone” design sessions. In those sessions, we’re planning to finalize our core-design principles taking all of the good stuff out there into account.

To be continued…

2 Likes

Thank you for the quick reply.

Our goal, however, is to be fully compatible with the concepts of Reactive Streams in our main APIs. For message handlers, this would mean you can return a Flux or Mono, for example, and Axon will nicely work with those (without blocking any threads).

This gives us a pretty good idea on how to continue discussions internally and what to expect in the future. Thank you.

But project reactor, as an example of a Reactive Streams implementation, is much more than just asynchronous execution of code. Project Loom will lack any flow control mechanisms, for example.

I agree wholeheartedly and this is something I think is often overlooked when discussing Loom vs Reactive.

Good luck with your upcoming design sessions. I look forward to hearing more about Axon 5 in the future.

3 Likes