Send Success notification when Command is executed
extract Action of the Commands simplify somme classes
This commit is contained in:
@@ -2,46 +2,144 @@ package eventDemo.app.command
|
||||
|
||||
import eventDemo.app.command.command.GameCommand
|
||||
import eventDemo.app.entity.Player
|
||||
import eventDemo.app.event.GameEventBus
|
||||
import eventDemo.app.event.GameEventHandler
|
||||
import eventDemo.app.event.event.GameEvent
|
||||
import eventDemo.app.notification.CommandErrorNotification
|
||||
import eventDemo.app.notification.CommandSuccessNotification
|
||||
import eventDemo.app.notification.Notification
|
||||
import eventDemo.libs.command.CommandStreamChannelBuilder
|
||||
import eventDemo.libs.command.CommandId
|
||||
import eventDemo.libs.command.CommandStreamChannel
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Listen [GameCommand] on [GameCommandStream], check the validity and execute an action.
|
||||
* Listen [GameCommand] on [CommandStreamChannel], check the validity and execute an action.
|
||||
*
|
||||
* This action can be executing an action and produce a new [GameEvent] after verification.
|
||||
*/
|
||||
class GameCommandHandler(
|
||||
private val commandStreamChannel: CommandStreamChannelBuilder<GameCommand>,
|
||||
private val commandStreamChannel: CommandStreamChannel<GameCommand>,
|
||||
private val eventHandler: GameEventHandler,
|
||||
private val runner: GameCommandActionRunner,
|
||||
eventBus: GameEventBus,
|
||||
listenerPriority: Int = DEFAULT_PRIORITY,
|
||||
) {
|
||||
private val logger = KotlinLogging.logger { }
|
||||
private val eventCommandMap = EventCommandMap()
|
||||
|
||||
companion object Config {
|
||||
const val DEFAULT_PRIORITY = 1000
|
||||
}
|
||||
|
||||
// subscribe to the event bus to send success notification after save the event.
|
||||
init {
|
||||
eventBus.subscribe(listenerPriority) { event: GameEvent ->
|
||||
eventCommandMap[event.eventId]?.apply {
|
||||
channel.sendSuccess(commandId)()
|
||||
} ?: logger.warn { "No Notification for event: $event" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the handler
|
||||
* Run a command and publish the event.
|
||||
*
|
||||
* It restricts to run only once a command.
|
||||
*
|
||||
* If the command fail, send an [error notification][CommandErrorNotification],
|
||||
* if success, send a [success notification][CommandSuccessNotification]
|
||||
*/
|
||||
suspend fun handle(
|
||||
player: Player,
|
||||
incomingCommandChannel: ReceiveChannel<GameCommand>,
|
||||
outgoingErrorChannelNotification: SendChannel<Notification>,
|
||||
channelNotification: SendChannel<Notification>,
|
||||
) =
|
||||
commandStreamChannel(incomingCommandChannel)
|
||||
.process { command ->
|
||||
if (command.payload.player.id != player.id) {
|
||||
commandStreamChannel.process(incomingCommandChannel) { command ->
|
||||
if (command.payload.player.id != player.id) {
|
||||
logger.atWarn {
|
||||
message = "Handle command Refuse, the player of the command is not the same: $command"
|
||||
payload = mapOf("command" to command)
|
||||
}
|
||||
channelNotification.sendError(command)("You are not the author of this command\n")
|
||||
} else {
|
||||
logger.atInfo {
|
||||
message = "Handle command: $command"
|
||||
payload = mapOf("command" to command)
|
||||
}
|
||||
try {
|
||||
val eventBuilder = runner.run(command)
|
||||
|
||||
eventHandler.handle(command.payload.aggregateId) { version ->
|
||||
eventBuilder(version)
|
||||
.also { eventCommandMap.set(it.eventId, channelNotification, command.id) }
|
||||
}
|
||||
} catch (e: CommandException) {
|
||||
logger.atWarn {
|
||||
message = "Handle command Refuse, the player of the command is not the same: $command"
|
||||
message = e.message
|
||||
payload = mapOf("command" to command)
|
||||
}
|
||||
nack()
|
||||
} else {
|
||||
logger.atInfo {
|
||||
message = "Handle command: $command"
|
||||
payload = mapOf("command" to command)
|
||||
}
|
||||
runner.run(command, outgoingErrorChannelNotification)
|
||||
channelNotification.sendError(command)(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SendChannel<Notification>.sendSuccess(commandId: CommandId): suspend () -> Unit =
|
||||
{
|
||||
val logger = KotlinLogging.logger { }
|
||||
CommandSuccessNotification(commandId = commandId)
|
||||
.also { notification ->
|
||||
logger.atDebug {
|
||||
message = "Notification SUCCESS sent"
|
||||
payload =
|
||||
mapOf(
|
||||
"notification" to notification,
|
||||
"commandId" to commandId,
|
||||
)
|
||||
}
|
||||
send(notification)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SendChannel<Notification>.sendError(command: GameCommand): suspend (String) -> Unit =
|
||||
{
|
||||
val logger = KotlinLogging.logger { }
|
||||
CommandErrorNotification(message = it, command = command)
|
||||
.also { notification ->
|
||||
logger.atWarn {
|
||||
message = "Notification ERROR sent: ${notification.message}"
|
||||
payload =
|
||||
mapOf(
|
||||
"notification" to notification,
|
||||
"command" to command,
|
||||
)
|
||||
}
|
||||
send(notification)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map to record the command that triggered the event.
|
||||
*/
|
||||
private class EventCommandMap {
|
||||
val map = ConcurrentHashMap<UUID, Output>()
|
||||
|
||||
fun set(
|
||||
eventId: UUID,
|
||||
channel: SendChannel<Notification>,
|
||||
commandId: CommandId,
|
||||
) {
|
||||
map[eventId] = Output(channel, commandId)
|
||||
}
|
||||
|
||||
operator fun get(eventId: UUID): Output? =
|
||||
map[eventId]
|
||||
|
||||
data class Output(
|
||||
val channel: SendChannel<Notification>,
|
||||
val commandId: CommandId,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user