akka / akka

Build highly concurrent, distributed, and resilient message-driven applications on the JVM
https://akka.io
Other
13.05k stars 3.59k forks source link

Async effects in PersistentBehavior #25650

Open patriknw opened 6 years ago

patriknw commented 6 years ago

Support for async effects has popped up a few times. Recently in the discussion about auto confirmations https://github.com/akka/akka/issues/25482#issuecomment-422734323

Let's first put this in context by coming up with a somewhat realistic example. Here is one that I can imagine.

  1. command to place an order
  2. persist event OrderPlaced
  3. send the order to the external order system, async call returning Future
  4. persist event OrderConfirmed when call is completed successfully
  5. reply with Done afterwards to the original requester

Retry mechanisms are left out for simplicity.

The way I would implement that is with ordinary actor facilities. When the Future is completed a new message is sent to self, which will persist the confirmation event and then finally reply. Stashing can be used in the meanwhile if only one order at a time can be handled (probably not realistic for this example). The ActorRef of the original requester can be carried along to the self message without problems.

This would also work with other interaction patterns than a Future, e.g. actor messages via tell or ActorContext.ask.

I don't see the need for a special async effect type.

I know, sending to the external order system might be better to do from an event processor (Akka Projections (read-side)), but let's say you don't want to complicate it by using an event processor.

raboof commented 6 years ago

I agree it would be better to implement this using ordinary actor features rather than introducing async effects: if we would implement this as a general feature, we would have to stash all messages while the async request is in flight. That is potentially a big cost that is not obvious when just looking at the code. When implementing this explicitly, the different possible behaviors become much more obvious.

It might make sense to cover this use case explicitly in the documentation, with a code example. This would achieve 'at-most-once' semantics when the actor system crashes between persisting and performing the async effect. A second example could extend the implementation with 'at-most-once' semantics by re-triggering the effect after recovery.

octonato commented 6 years ago

I agree that this could be implemented using ordinary actor features and it’s on the user to choose what to do. Stashing is not a requirement. For instance, you could change behaviour and reject new commands while awaiting some confirmation. It’s own the user to build that up.

On the topic of using projections, I think both cases are valid and we can summarise it as:

This also allows us to build Sagas or Process Managers using a PersistentBehavior. And I don’t think this is something that should be provided out-of-the-box because there is a myriad of possible implementations. Instead we have flexible building blocks and let the users choose and assemble they way they prefer.

viktorklang commented 6 years ago

I think it needs to be built in. The second a user needs to call something which returns a Future as a part of their command processing, they're currently going to do Await.

ktoso commented 6 years ago

See also https://github.com/akka/akka/issues/17522 from 2015

patriknw commented 6 years ago

ok, I'll think more about this. Somewhat related to https://github.com/akka/akka/pull/24809, which is also about stashing until a Future is completed.

I still think that when such Future is completed it should result in a new command (message) and not be based on nested callbacks (hell).