I have a situation where, on the first Command received by my behavior, I need to first retrieve some data from a DB to fully initialize the State, and only then process the Command.
What is the best way to do this?
I have tried doing something like the below. In the *** sections, do I need to use context.pipeToSelf again, and if I do, will the original command be processed after the state is updated?
def uninitializedCommandHandler(context, state, command) {
command match {
case InitializeCommand(dataFromDb, originalCommand) =>
// *** can I send the originalCommand to myself here, or should I do it in the event handler ***
Effect.persist(InitializedEvent(state.copy(data = dataFromDb), maybeOriginalCommand)
case anyOtherCommand =>
// here we need to load the data
context.pipeToSelf(loadData) { case Success(data) => InitializeCommand(data, originalCommand) }
Effect.none
}
}
def handleEvent(context, state, event) {
event match {
case InitializedEvent(dataFromDb, originalCommand) =>
// *** should I send the originalCommand to myself here? ***
state.copy(data = dataFromDb)
}
}
Thanks in advance.
The event handler must be a pure function that does not have any side effects but only use the state and the event passed to it to create a new state.
Best is probably to either do that just like you have started implementing, in a command handler of the ESB itself turning it into another command that contains all that is needed, or alternatively in another actor or composed future in front of the ESB (Event Sourced Behavior) actor, combining the command and the needed additional data into a single command that the event sourced behavior can process, store as one event, and apply atomically to the state.
This will highlight at least two things to consider:
- Other commands could come in between receiving the original command and it completing loading data, should those be handled, or delayed until fetching is done?
- The risk of the ESB stopping/crashing in between receiving the triggering command and asynchronously fetching additional data completing, for example if you run it in sharding and it is rebalanced between nodes – if you need a guarantee that happens without a client retrying the original command you will need to store an event with the intent to load that data (all the details of the original command) and re-trigger on recovery.
Thanks, Johan,
I actually wanted to do this, “or alternatively in another actor or composed future in front of the ESB (Event Sourced Behavior) actor, combining the command and the needed additional data into a single command that the event sourced behavior can process, store as one event, and apply atomically to the state.” but it was unclear to me how to get a reference to the ESB to send such a command to it before any other commands are processed.
If I do continue with the current approach, I would need to use context.pipeToSelf in the command handler, correct?
Yes, turning the completed future into a command including the original command like that looks reasonable.
You just have to consider what it means for you use case if additional commands arrive between InitializeCommand
and the time when the data has successfully loaded and put in the inbox.