create GameCommandRunner to extract business logic

This commit is contained in:
2025-03-11 21:59:17 +01:00
parent a2756d7fdb
commit 28075feff2
9 changed files with 80 additions and 37 deletions

View File

@@ -0,0 +1,29 @@
package eventDemo.app.command
import eventDemo.app.command.command.GameCommand
import eventDemo.app.notification.ErrorNotification
import eventDemo.app.notification.Notification
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.channels.SendChannel
typealias ErrorNotifier = suspend (String) -> Unit
fun errorNotifier(
command: GameCommand,
channel: SendChannel<Notification>,
): ErrorNotifier =
{
val logger = KotlinLogging.logger { }
ErrorNotification(message = it)
.let { notification ->
logger.atWarn {
message = "Notification send ERROR: ${notification.message}"
payload =
mapOf(
"notification" to notification,
"command" to command,
)
}
channel.send(notification)
}
}

View File

@@ -1,15 +1,8 @@
package eventDemo.app.command package eventDemo.app.command
import eventDemo.app.command.command.GameCommand import eventDemo.app.command.command.GameCommand
import eventDemo.app.command.command.ICantPlayCommand
import eventDemo.app.command.command.IWantToJoinTheGameCommand
import eventDemo.app.command.command.IWantToPlayCardCommand
import eventDemo.app.command.command.IamReadyToPlayCommand
import eventDemo.app.entity.Player import eventDemo.app.entity.Player
import eventDemo.app.event.GameEventHandler
import eventDemo.app.event.event.GameEvent import eventDemo.app.event.event.GameEvent
import eventDemo.app.event.projection.GameStateRepository
import eventDemo.app.notification.ErrorNotification
import eventDemo.app.notification.Notification import eventDemo.app.notification.Notification
import eventDemo.libs.command.CommandStreamChannelBuilder import eventDemo.libs.command.CommandStreamChannelBuilder
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
@@ -22,9 +15,8 @@ import kotlinx.coroutines.channels.SendChannel
* This action can be executing an action and produce a new [GameEvent] after verification. * This action can be executing an action and produce a new [GameEvent] after verification.
*/ */
class GameCommandHandler( class GameCommandHandler(
private val eventHandler: GameEventHandler,
private val gameStateRepository: GameStateRepository,
private val commandStreamChannel: CommandStreamChannelBuilder<GameCommand>, private val commandStreamChannel: CommandStreamChannelBuilder<GameCommand>,
private val runner: GameCommandRunner,
) { ) {
private val logger = KotlinLogging.logger { } private val logger = KotlinLogging.logger { }
@@ -48,30 +40,7 @@ class GameCommandHandler(
message = "Handle command: $command" message = "Handle command: $command"
payload = mapOf("command" to command) payload = mapOf("command" to command)
} }
command.run(outgoingErrorChannelNotification) runner.run(command, outgoingErrorChannelNotification)
} }
} }
private suspend fun GameCommand.run(outgoingErrorChannelNotification: SendChannel<Notification>) {
val gameState = gameStateRepository.get(payload.gameId)
val playerErrorNotifier = errorNotifier(outgoingErrorChannelNotification)
when (this) {
is IWantToPlayCardCommand -> run(gameState, playerErrorNotifier, eventHandler)
is IamReadyToPlayCommand -> run(gameState, playerErrorNotifier, eventHandler)
is IWantToJoinTheGameCommand -> run(gameState, playerErrorNotifier, eventHandler)
is ICantPlayCommand -> run(gameState, playerErrorNotifier, eventHandler)
}
}
} }
fun errorNotifier(channel: SendChannel<Notification>): suspend (String) -> Unit =
{
val logger = KotlinLogging.logger { }
val notification = ErrorNotification(message = it)
logger.atWarn {
message = "Notification send ERROR: ${notification.message}"
payload = mapOf("notification" to notification)
}
channel.send(notification)
}

View File

@@ -0,0 +1,31 @@
package eventDemo.app.command
import eventDemo.app.command.command.GameCommand
import eventDemo.app.command.command.ICantPlayCommand
import eventDemo.app.command.command.IWantToJoinTheGameCommand
import eventDemo.app.command.command.IWantToPlayCardCommand
import eventDemo.app.command.command.IamReadyToPlayCommand
import eventDemo.app.event.GameEventHandler
import eventDemo.app.event.projection.GameStateRepository
import eventDemo.app.notification.Notification
import kotlinx.coroutines.channels.SendChannel
class GameCommandRunner(
private val eventHandler: GameEventHandler,
private val gameStateRepository: GameStateRepository,
) {
suspend fun run(
command: GameCommand,
outgoingErrorChannelNotification: SendChannel<Notification>,
) {
val gameState = gameStateRepository.get(command.payload.gameId)
val errorNotifier = errorNotifier(command, outgoingErrorChannelNotification)
when (command) {
is IWantToPlayCardCommand -> command.run(gameState, errorNotifier, this.eventHandler)
is IamReadyToPlayCommand -> command.run(gameState, errorNotifier, this.eventHandler)
is IWantToJoinTheGameCommand -> command.run(gameState, errorNotifier, this.eventHandler)
is ICantPlayCommand -> command.run(gameState, errorNotifier, this.eventHandler)
}
}
}

View File

@@ -1,5 +1,6 @@
package eventDemo.app.command.command package eventDemo.app.command.command
import eventDemo.app.command.ErrorNotifier
import eventDemo.app.entity.GameId import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player import eventDemo.app.entity.Player
import eventDemo.app.event.GameEventHandler import eventDemo.app.event.GameEventHandler
@@ -25,7 +26,7 @@ data class ICantPlayCommand(
suspend fun run( suspend fun run(
state: GameState, state: GameState,
playerErrorNotifier: suspend (String) -> Unit, playerErrorNotifier: ErrorNotifier,
eventHandler: GameEventHandler, eventHandler: GameEventHandler,
) { ) {
if (state.currentPlayerTurn != payload.player) { if (state.currentPlayerTurn != payload.player) {

View File

@@ -1,5 +1,6 @@
package eventDemo.app.command.command package eventDemo.app.command.command
import eventDemo.app.command.ErrorNotifier
import eventDemo.app.entity.GameId import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player import eventDemo.app.entity.Player
import eventDemo.app.event.GameEventHandler import eventDemo.app.event.GameEventHandler
@@ -26,7 +27,7 @@ data class IWantToJoinTheGameCommand(
suspend fun run( suspend fun run(
state: GameState, state: GameState,
playerErrorNotifier: suspend (String) -> Unit, playerErrorNotifier: ErrorNotifier,
eventHandler: GameEventHandler, eventHandler: GameEventHandler,
) { ) {
val logger = KotlinLogging.logger {} val logger = KotlinLogging.logger {}

View File

@@ -1,5 +1,6 @@
package eventDemo.app.command.command package eventDemo.app.command.command
import eventDemo.app.command.ErrorNotifier
import eventDemo.app.entity.Card import eventDemo.app.entity.Card
import eventDemo.app.entity.GameId import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player import eventDemo.app.entity.Player
@@ -27,7 +28,7 @@ data class IWantToPlayCardCommand(
suspend fun run( suspend fun run(
state: GameState, state: GameState,
playerErrorNotifier: suspend (String) -> Unit, playerErrorNotifier: ErrorNotifier,
eventHandler: GameEventHandler, eventHandler: GameEventHandler,
) { ) {
if (!state.isStarted) { if (!state.isStarted) {

View File

@@ -1,5 +1,6 @@
package eventDemo.app.command.command package eventDemo.app.command.command
import eventDemo.app.command.ErrorNotifier
import eventDemo.app.entity.GameId import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player import eventDemo.app.entity.Player
import eventDemo.app.event.GameEventHandler import eventDemo.app.event.GameEventHandler
@@ -25,7 +26,7 @@ data class IamReadyToPlayCommand(
suspend fun run( suspend fun run(
state: GameState, state: GameState,
playerErrorNotifier: suspend (String) -> Unit, playerErrorNotifier: ErrorNotifier,
eventHandler: GameEventHandler, eventHandler: GameEventHandler,
) { ) {
val playerExist: Boolean = state.players.contains(payload.player) val playerExist: Boolean = state.players.contains(payload.player)

View File

@@ -1,6 +1,7 @@
package eventDemo.configuration package eventDemo.configuration
import eventDemo.app.command.GameCommandHandler import eventDemo.app.command.GameCommandHandler
import eventDemo.app.command.GameCommandRunner
import eventDemo.app.command.command.GameCommand import eventDemo.app.command.command.GameCommand
import eventDemo.app.event.GameEventBus import eventDemo.app.event.GameEventBus
import eventDemo.app.event.GameEventHandler import eventDemo.app.event.GameEventHandler
@@ -40,6 +41,7 @@ val appKoinModule =
} }
singleOf(::GameEventHandler) singleOf(::GameEventHandler)
singleOf(::GameCommandRunner)
singleOf(::GameCommandHandler) singleOf(::GameCommandHandler)
singleOf(::PlayerNotificationEventListener) singleOf(::PlayerNotificationEventListener)
} }

View File

@@ -0,0 +1,8 @@
package eventDemo.app.command
import io.kotest.core.spec.style.FunSpec
class GameCommandRunnerTest :
FunSpec({
test("run should run the correct command") { }
})