create bus and subscriber
This commit is contained in:
73
src/main/kotlin/eventDemo/app/actions/GameCommandHandler.kt
Normal file
73
src/main/kotlin/eventDemo/app/actions/GameCommandHandler.kt
Normal file
@@ -0,0 +1,73 @@
|
||||
package eventDemo.app.actions
|
||||
|
||||
import eventDemo.app.actions.playNewCard.PlayCardCommand
|
||||
import eventDemo.shared.command.GameCommandStream
|
||||
import eventDemo.shared.entity.Player
|
||||
import eventDemo.shared.event.CardIsPlayedEvent
|
||||
import eventDemo.shared.event.GameEvent
|
||||
import eventDemo.shared.event.GameEventStream
|
||||
import eventDemo.shared.event.GameState
|
||||
import eventDemo.shared.event.buildStateFromEventStream
|
||||
import io.ktor.websocket.Frame
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
* Listen [PlayCardCommand] on [GameCommandStream], check the validity and execute the action.
|
||||
*
|
||||
* This action can be executing an action and produce a new [GameEvent] after verification.
|
||||
*/
|
||||
class GameCommandHandler(
|
||||
private val eventStream: GameEventStream,
|
||||
incoming: ReceiveChannel<Frame>,
|
||||
outgoing: SendChannel<Frame>,
|
||||
) {
|
||||
private val commandStream = GameCommandStream(incoming, outgoing)
|
||||
private val playerNotifier = outgoing
|
||||
|
||||
/**
|
||||
* Init the handler
|
||||
*/
|
||||
fun init(player: Player) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
commandStream.process {
|
||||
if (it.payload.player.id != player.id) {
|
||||
nack()
|
||||
}
|
||||
when (it) {
|
||||
is PlayCardCommand -> {
|
||||
// Check the command can be executed
|
||||
val canBeExecuted =
|
||||
it.payload.gameId
|
||||
.buildStateFromEventStream(eventStream)
|
||||
.commandCardCanBeExecuted(it)
|
||||
|
||||
if (canBeExecuted) {
|
||||
eventStream.publish(
|
||||
CardIsPlayedEvent(
|
||||
it.payload.gameId,
|
||||
it.payload.card,
|
||||
it.payload.player,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
runBlocking {
|
||||
playerNotifier.send(Frame.Text("Command cannot be executed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun GameState.commandCardCanBeExecuted(command: PlayCardCommand): Boolean =
|
||||
canBePlayThisCard(
|
||||
command.payload.player,
|
||||
command.payload.card,
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
package eventDemo.app.actions
|
||||
|
||||
import eventDemo.libs.event.EventBus
|
||||
import eventDemo.shared.GameId
|
||||
import eventDemo.shared.event.GameEvent
|
||||
import eventDemo.shared.toFrame
|
||||
import io.ktor.websocket.Frame
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class GameEventPlayerNotificationSubscriber(
|
||||
private val eventBus: EventBus<GameEvent, GameId>,
|
||||
private val outgoing: SendChannel<Frame>,
|
||||
) {
|
||||
fun init() {
|
||||
eventBus.subscribe { event: GameEvent ->
|
||||
runBlocking {
|
||||
outgoing.send(event.toFrame())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package eventDemo.app.actions
|
||||
|
||||
import eventDemo.libs.event.EventBus
|
||||
import eventDemo.libs.event.EventStream
|
||||
import eventDemo.shared.GameId
|
||||
import eventDemo.shared.event.GameEvent
|
||||
import eventDemo.shared.event.GameStartedEvent
|
||||
import eventDemo.shared.event.buildStateFromEventStream
|
||||
|
||||
class GameEventReactionSubscriber(
|
||||
private val eventBus: EventBus<GameEvent, GameId>,
|
||||
private val eventStream: EventStream<GameEvent, GameId>,
|
||||
) {
|
||||
fun init() {
|
||||
eventBus.subscribe { event: GameEvent ->
|
||||
val state = event.id.buildStateFromEventStream(eventStream)
|
||||
if (state.isReady) {
|
||||
eventStream.publish(
|
||||
GameStartedEvent.new(
|
||||
state.gameId,
|
||||
state.players,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
package eventDemo.app.actions.playNewCard
|
||||
|
||||
import eventDemo.libs.command.send
|
||||
import eventDemo.plugins.GameIdSerializer
|
||||
import eventDemo.shared.GameId
|
||||
import eventDemo.shared.command.GameCommandStream
|
||||
import eventDemo.shared.command.GameCommandStreamInMemory
|
||||
import eventDemo.shared.entity.Card
|
||||
import eventDemo.shared.entity.Game
|
||||
import eventDemo.shared.entity.Player
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.resources.Resource
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.auth.authenticate
|
||||
import io.ktor.server.auth.principal
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.resources.post
|
||||
import io.ktor.server.response.respondNullable
|
||||
@@ -16,12 +17,11 @@ import io.ktor.server.routing.Routing
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.ktor.ext.inject
|
||||
|
||||
@Serializable
|
||||
@Resource("/game/{id}")
|
||||
class GameRoute(
|
||||
@Serializable(with = GameIdSerializer::class)
|
||||
// @Serializable(with = GameIdSerializer::class)
|
||||
val id: GameId,
|
||||
) {
|
||||
@Serializable
|
||||
@@ -35,19 +35,27 @@ class GameRoute(
|
||||
* API route to send a request to play card.
|
||||
*/
|
||||
fun Routing.playNewCard() {
|
||||
val commandStream by inject<GameCommandStream>()
|
||||
val commandStream = GameCommandStreamInMemory()
|
||||
authenticate {
|
||||
/*
|
||||
* A player request to play a new card.
|
||||
*
|
||||
* It always returns [HttpStatusCode.OK], but it is not mean that card is already played!
|
||||
*/
|
||||
post<GameRoute.Card> {
|
||||
val card = call.receive<Card>()
|
||||
val name = call.principal<Player>()!!
|
||||
launch(Dispatchers.Default) {
|
||||
commandStream.send(
|
||||
PlayCardCommand(
|
||||
it.game.id,
|
||||
name,
|
||||
card,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* A player request to play a new card.
|
||||
*
|
||||
* It always returns [HttpStatusCode.OK], but it is not mean that card is already played!
|
||||
*/
|
||||
post<GameRoute.Card> {
|
||||
val card = call.receive<Card>()
|
||||
launch(Dispatchers.Default) {
|
||||
commandStream.send(PlayCardCommand(Game(it.game.id), card))
|
||||
call.respondNullable<Any?>(HttpStatusCode.OK, null)
|
||||
}
|
||||
|
||||
call.respondNullable<Any?>(HttpStatusCode.OK, null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ package eventDemo.app.actions.playNewCard
|
||||
|
||||
import eventDemo.libs.command.Command
|
||||
import eventDemo.libs.command.CommandId
|
||||
import eventDemo.shared.GameId
|
||||
import eventDemo.shared.entity.Card
|
||||
import eventDemo.shared.entity.Game
|
||||
import eventDemo.shared.entity.Player
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -13,19 +14,32 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
@SerialName("PlayCard")
|
||||
data class PlayCardCommand(
|
||||
val payload: Payload,
|
||||
) : Command {
|
||||
override val payload: Payload,
|
||||
) : GameCommand {
|
||||
constructor(
|
||||
game: Game,
|
||||
gameId: GameId,
|
||||
player: Player,
|
||||
card: Card,
|
||||
) : this(Payload(game, card))
|
||||
) : this(Payload(gameId, player, card))
|
||||
|
||||
override val name: String = "PlayCard"
|
||||
override val id: CommandId = CommandId()
|
||||
|
||||
@Serializable
|
||||
data class Payload(
|
||||
val game: Game,
|
||||
override val gameId: GameId,
|
||||
override val player: Player,
|
||||
val card: Card,
|
||||
)
|
||||
) : GameCommand.Payload
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed interface GameCommand : Command {
|
||||
val payload: Payload
|
||||
|
||||
@Serializable
|
||||
sealed interface Payload {
|
||||
val gameId: GameId
|
||||
val player: Player
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package eventDemo.app.actions.playNewCard
|
||||
|
||||
import eventDemo.shared.command.GameCommandStream
|
||||
import eventDemo.shared.event.CardIsPlayedEvent
|
||||
import eventDemo.shared.event.GameEventStream
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Listen [PlayCardCommand] on [GameCommandStream], check the validity and execute the action.
|
||||
*
|
||||
* This action produces a new [CardIsPlayedEvent]
|
||||
*/
|
||||
class PlayCardCommandHandler(
|
||||
private val commandStream: GameCommandStream,
|
||||
private val eventStream: GameEventStream,
|
||||
) {
|
||||
/**
|
||||
* Init the handler
|
||||
*/
|
||||
fun init() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
commandStream.process {
|
||||
// TODO check the command can be executed
|
||||
eventStream.publish(CardIsPlayedEvent(it.payload.game.id, it.payload.card))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user