refactoring: Masive refactor to build the V2
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
class CommandException(
|
||||
override val message: String,
|
||||
) : Exception(message)
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.GameCommand
|
||||
import eventDemo.contexts.game.application.command.models.JoinTheGameCommand
|
||||
import eventDemo.contexts.game.application.command.models.PlayCardCommand
|
||||
import eventDemo.contexts.game.application.command.models.ReadyToPlayCommand
|
||||
import eventDemo.contexts.game.application.command.models.TakeCartFromDrawPileCommand
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import java.util.Collections
|
||||
|
||||
class GameCommandHandlerDispatcher(
|
||||
private val playCardHandler: PlayCardHandler,
|
||||
private val readyToPlayHandler: ReadyToPlayHandler,
|
||||
private val joinTheGameHandler: JoinTheGameHandler,
|
||||
private val takeCartFromDrawPileHandler: TakeCartFromDrawPileHandler,
|
||||
) {
|
||||
companion object {
|
||||
val lock: MutableMap<GameId, String> = Collections.synchronizedMap(mutableMapOf())
|
||||
}
|
||||
|
||||
fun dispatch(command: GameCommand) {
|
||||
synchronized(lock.getOrPut(command.payload.aggregateId) { command.payload.aggregateId.toString() }) {
|
||||
when (command) {
|
||||
is JoinTheGameCommand -> joinTheGameHandler.handle(command)
|
||||
is ReadyToPlayCommand -> readyToPlayHandler.handle(command)
|
||||
is PlayCardCommand -> playCardHandler.handle(command)
|
||||
is TakeCartFromDrawPileCommand -> takeCartFromDrawPileHandler.handle(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.GameCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.events.GameEvent
|
||||
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||
import eventDemo.libs.command.Command
|
||||
import eventDemo.libs.eventSource.eventStore.VersionConflictException
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
sealed interface CommandHandler<C : Command> {
|
||||
fun handle(command: C)
|
||||
}
|
||||
|
||||
abstract class GameEventManager(
|
||||
private val gameRepository: GameRepository,
|
||||
private val gameEventBus: GameEventBus,
|
||||
) {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
fun GameCommand.getGame(): Game =
|
||||
gameRepository.get(payload.aggregateId) ?: error("Game not found")
|
||||
|
||||
fun GameEvent.getGame(): Game =
|
||||
gameRepository.get(aggregateId) ?: error("Game not found")
|
||||
|
||||
@Throws(VersionConflictException::class)
|
||||
protected fun Game.saveEvents(): Game {
|
||||
gameRepository.save(this)
|
||||
return this
|
||||
}
|
||||
|
||||
protected fun Game.publishEvents(): Game {
|
||||
gameEventBus.publish(recordedEvents)
|
||||
return this
|
||||
}
|
||||
|
||||
protected fun <G : Game> Game.isStatusOrFail(
|
||||
kClass: KClass<G>,
|
||||
message: String,
|
||||
): G {
|
||||
if (kClass.isInstance(this)) {
|
||||
return this as G
|
||||
} else {
|
||||
throw CommandException(message)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun <T> retry(
|
||||
mapAttempts: Int = 5,
|
||||
block: () -> T,
|
||||
): T =
|
||||
try {
|
||||
block()
|
||||
} catch (e: VersionConflictException) {
|
||||
if (mapAttempts > 0) {
|
||||
logger.warn { "retry after version conflict (attempts left: $mapAttempts)" }
|
||||
retry(mapAttempts - 1, block)
|
||||
} else {
|
||||
logger.error { "Version conflict retry failed" }
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.auth.application.eventStores.UserRepository
|
||||
import eventDemo.contexts.game.application.command.models.JoinTheGameCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameCreated
|
||||
|
||||
/**
|
||||
* A command to perform an action to play a new card
|
||||
*/
|
||||
class JoinTheGameHandler(
|
||||
gameRepository: GameRepository,
|
||||
gameEventBus: GameEventBus,
|
||||
private val userRepository: UserRepository,
|
||||
) : GameEventManager(gameRepository, gameEventBus),
|
||||
CommandHandler<JoinTheGameCommand> {
|
||||
override fun handle(command: JoinTheGameCommand) {
|
||||
val user = userRepository.get(command.userId) ?: error("User with id ${command.userId} doesn't exist")
|
||||
retry {
|
||||
command
|
||||
.getGame()
|
||||
.isStatusOrFail(GameCreated::class, "The game is started")
|
||||
.userJoinTheGame(
|
||||
userId = command.userId,
|
||||
name = user.username,
|
||||
).saveEvents()
|
||||
.publishEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.PlayCardCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
||||
|
||||
/**
|
||||
* A command to perform an action to play a new card
|
||||
*/
|
||||
class PlayCardHandler(
|
||||
gameRepository: GameRepository,
|
||||
gameEventBus: GameEventBus,
|
||||
) : GameEventManager(gameRepository, gameEventBus),
|
||||
CommandHandler<PlayCardCommand> {
|
||||
override fun handle(command: PlayCardCommand) {
|
||||
command
|
||||
.getGame()
|
||||
.isStatusOrFail(GameStarted::class, "The game is not started")
|
||||
.playTheCard(
|
||||
card = command.payload.card,
|
||||
playerId = command.payload.playerId,
|
||||
chosenColor = command.payload.chosenColor,
|
||||
).saveEvents()
|
||||
.publishEvents()
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.ReadyToPlayCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameCreated
|
||||
|
||||
/**
|
||||
* A command to set as ready to play
|
||||
*/
|
||||
class ReadyToPlayHandler(
|
||||
gameRepository: GameRepository,
|
||||
gameEventBus: GameEventBus,
|
||||
) : GameEventManager(gameRepository, gameEventBus),
|
||||
CommandHandler<ReadyToPlayCommand> {
|
||||
override fun handle(command: ReadyToPlayCommand) {
|
||||
command
|
||||
.getGame()
|
||||
.isStatusOrFail(GameCreated::class, "The game is started")
|
||||
.setReadyPlayer(command.payload.playerId)
|
||||
.saveEvents()
|
||||
.publishEvents()
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package eventDemo.contexts.game.application.command.handlers
|
||||
|
||||
import eventDemo.contexts.game.application.command.models.TakeCartFromDrawPileCommand
|
||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||
import eventDemo.contexts.game.application.ports.GameEventBus
|
||||
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
||||
|
||||
/**
|
||||
* A command to draw card on draw pile.
|
||||
*
|
||||
* Is can be triggered when you cannot play any card in your hand.
|
||||
*/
|
||||
class TakeCartFromDrawPileHandler(
|
||||
gameRepository: GameRepository,
|
||||
gameEventBus: GameEventBus,
|
||||
) : GameEventManager(gameRepository, gameEventBus),
|
||||
CommandHandler<TakeCartFromDrawPileCommand> {
|
||||
override fun handle(command: TakeCartFromDrawPileCommand) {
|
||||
command
|
||||
.getGame()
|
||||
.isStatusOrFail(GameStarted::class, "The game is not started")
|
||||
.playerTakeCartFromDrawPile(command.payload.playerId, 1)
|
||||
.saveEvents()
|
||||
.publishEvents()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package eventDemo.contexts.game.application.command.models
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.GameIdSerializer
|
||||
import eventDemo.libs.command.Command
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed interface GameCommand : Command {
|
||||
val userId: UserId
|
||||
val payload: Payload
|
||||
|
||||
@Serializable
|
||||
sealed interface Payload {
|
||||
@Serializable(with = GameIdSerializer::class)
|
||||
val aggregateId: GameId
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package eventDemo.contexts.game.application.command.models
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.GameIdSerializer
|
||||
import eventDemo.libs.command.CommandId
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A command to perform an action to play a new card
|
||||
*/
|
||||
@Serializable
|
||||
data class JoinTheGameCommand(
|
||||
override val userId: UserId,
|
||||
override val payload: Payload,
|
||||
) : GameCommand {
|
||||
override val id: CommandId = CommandId()
|
||||
|
||||
@Serializable
|
||||
data class Payload(
|
||||
@Serializable(with = GameIdSerializer::class)
|
||||
override val aggregateId: GameId,
|
||||
) : GameCommand.Payload
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package eventDemo.contexts.game.application.command.models
|
||||
|
||||
import eventDemo.contexts.game.domain.game.Card
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.GameIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||
import eventDemo.libs.command.CommandId
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A command to perform an action to play a new card
|
||||
*/
|
||||
@Serializable
|
||||
data class PlayCardCommand(
|
||||
override val userId: UserId,
|
||||
override val payload: Payload,
|
||||
) : GameCommand {
|
||||
override val id: CommandId = CommandId()
|
||||
|
||||
@Serializable
|
||||
data class Payload(
|
||||
@Serializable(with = GameIdSerializer::class)
|
||||
override val aggregateId: GameId,
|
||||
@Serializable(with = PlayerIdSerializer::class)
|
||||
val playerId: Player.PlayerId,
|
||||
val card: Card,
|
||||
val chosenColor: Card.Color?,
|
||||
) : GameCommand.Payload
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package eventDemo.contexts.game.application.command.models
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.GameIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||
import eventDemo.libs.command.CommandId
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A command to set as ready to play
|
||||
*/
|
||||
@Serializable
|
||||
data class ReadyToPlayCommand(
|
||||
override val userId: UserId,
|
||||
override val payload: Payload,
|
||||
) : GameCommand {
|
||||
override val id: CommandId = CommandId()
|
||||
|
||||
@Serializable
|
||||
data class Payload(
|
||||
@Serializable(with = GameIdSerializer::class)
|
||||
override val aggregateId: GameId,
|
||||
@Serializable(with = PlayerIdSerializer::class)
|
||||
val playerId: Player.PlayerId,
|
||||
) : GameCommand.Payload
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package eventDemo.contexts.game.application.command.models
|
||||
|
||||
import eventDemo.contexts.game.domain.game.GameId
|
||||
import eventDemo.contexts.game.domain.game.Player
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.GameIdSerializer
|
||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||
import eventDemo.libs.command.CommandId
|
||||
import eventDemo.sharedKernel.UserId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A command to perform an action to play a new card
|
||||
*/
|
||||
@Serializable
|
||||
data class TakeCartFromDrawPileCommand(
|
||||
override val userId: UserId,
|
||||
override val payload: Payload,
|
||||
) : GameCommand {
|
||||
override val id: CommandId = CommandId()
|
||||
|
||||
@Serializable
|
||||
data class Payload(
|
||||
@Serializable(with = GameIdSerializer::class)
|
||||
override val aggregateId: GameId,
|
||||
@Serializable(with = PlayerIdSerializer::class)
|
||||
val playerId: Player.PlayerId,
|
||||
) : GameCommand.Payload
|
||||
}
|
||||
Reference in New Issue
Block a user