How long it really takes? Excluding the latency between write and read side?
I don’t know the domain, but at first sight, I would say, the entity should be responsible for this calculation. The entity is the owner of the data and should be the sole responsible for running a business process that depends on its data.
A command called WinnerSelectedCommand is also strange. It reveals that someone else took the decision of selecting a winner and is now just passing this information to the entity. A command is usually in the imperative tense, eg: SelectAWinner, which eventually emit the event WinnerSelected (in the past tense).
WinnerSelectedCommand looks more like a notification of something that already happened on another part of the system, thus it looks like an event disguised in command.
Long calculation in command handlers are, from our experience, a bad practice.
There is a limitation on command ask timeout so calculation time is not flexible.
“command - event (change behavior) - event processor - command - event (change behavour) ” is a pattern that we offten use in cases like this. It is simple saga pattern.
We look at event processor, in this case, as a “heavy lifter” for the entity :)
We also suggest changing behaviors between events to ensure right saga flow in the entity.
I think the key point here is how long a “long computation” is.
Considering that a PersistentEntity is the equivalent of an Aggregate in DDD parlance, a command handler should only operate on the data available at hand, which can only be the data passed in the command and the state of the Entity itself.
How long can such a computation be?
If an Entity is fulfilling its duty of being the guardian of the consistency of a domain model, then it only operates on data that is already in memory and in scope. There should be no IO or remote calls inside an Entity because that would mean that it’s not in possession of the data it’s supposed to guard against inconsistencies.
In any case, if there is such a heavy computation (that still only operates on data in scope), you can always increase the timeout of an ask call.
Moreover, and I know that this is an unpopular topic, there is no async command handlers in Lagom for the reasons I just mentioned. That said, one needs to call blocking APIs if they want to do really heavy computations. And again, that will be against the basic principles of aggregate design.
even if you know, approximatly, how long the calculation will last this is only one parameter that need to be taken into account when defining ask timeout.
You also need to take in account loading of the entity which time drastically depends on how many entityies are loaded in parallel. From my expirience defining right ask timeout value is not trivial.
Also, as you mentioned, command handler is limited with blacking apis but there is a logical reason why (guardian of consistency as you have mentioned).
So with right attention, using entity with event processor could be treated as an aggregate root with ability of heavy calculations.
This would be a good feature in entity itself. Like integrated saga processor for cases like this.
Maybe you have some other solution that could be used in situations like this becaue command handler is not a silver bullet cases like this.
@octonato I have fixe the error with WinnerSelectedCommand - it should be SelectWinnerCommand or maybe even UpdateTheWinnerCommad :).
@aklikic I will stick to your pattern if you use it offten. Can you explain why you change behavior after the event? Is that necessary? Could you provide a simple description of your simplest case?
The behaviour switchinhg is needed to avoid, for instance, that two SelectWinnerCommand are processed. You switch to a behavior in which you can’t process new commands or some kind of commands while you wait the confirmation from the read-side processor.
Change of the behavior is not mandatory but a flow is more clear and forces required command handlers per part of the flow.
So in your case:
GameEnd cmd - GameEnded event (change to game-ended-selecting-winner behavior) - event processor does calculation in GameEnded event handler and sends SetSelectedWinner cmd - WinnerSelected event (change to game-ended-winner-selected behavior).
SelectWinnerDone commande handler is only enabled in game-ended-selecting-winner behavior ensuring that GameEnded event was issued.
10 seconds of pure calculations using in scope data? Wow! Looks like a pretty complex game.
You can send me the repo, but I don’t think I will have time to look into it. So please, don’t count on this.
Anyway, I hold to my previous point. This calculation, although long, seems to be something that should be handled by the model itself as it’s the core business of the game model.
The technique that @aklikic explained is a good option when you need sagas and where latency is not an issue. The case you describe doesn’t seem to fit it.