create CommandStream and first Command

This commit is contained in:
2024-02-29 01:29:08 +01:00
parent 8beb66d8dc
commit 53cc961c62
12 changed files with 233 additions and 7 deletions

View File

@@ -1,5 +1,7 @@
package eventDemo.app
import eventDemo.plugins.GameIdSerializer
import kotlinx.serialization.Serializable
import java.util.UUID
sealed interface AggregateId {
@@ -7,6 +9,7 @@ sealed interface AggregateId {
}
@JvmInline
@Serializable(with = GameIdSerializer::class)
value class GameId(override val id: UUID = UUID.randomUUID()) : AggregateId {
constructor(id: String) : this(UUID.fromString(id))

View File

@@ -3,6 +3,17 @@ package eventDemo.app
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Game(
val id: GameId,
) {
companion object {
fun new(): Game {
return Game(GameId())
}
}
}
@Serializable
sealed interface Card {
@Serializable

View File

@@ -0,0 +1,40 @@
package eventDemo.app
import eventDemo.plugins.CommandIdSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.util.UUID
@JvmInline
@Serializable(with = CommandIdSerializer::class)
value class CommandId(private val id: UUID = UUID.randomUUID()) {
constructor(id: String) : this(UUID.fromString(id))
override fun toString(): String = id.toString()
}
@Serializable
sealed interface Command {
val id: CommandId
val name: String
}
@Serializable
@SerialName("PlayCard")
data class PlayCardCommand(
val payload: Payload,
) : Command {
constructor(
game: Game,
card: Card,
) : this(Payload(game, card))
override val name: String = "PlayCard"
override val id: CommandId = CommandId()
@Serializable
data class Payload(
val game: Game,
val card: Card,
)
}

View File

@@ -0,0 +1,28 @@
package eventDemo.app
import io.github.oshai.kotlinlogging.KotlinLogging
class CommandStream {
private val logger = KotlinLogging.logger {}
private val commandBus: MutableList<Command> = mutableListOf()
fun sendRequest(command: Command) {
commandBus.add(command)
logger.atInfo {
message = "Command published: $command"
payload = mapOf("command" to command)
}
}
fun sendRequest(vararg commands: Command) {
commands.forEach { sendRequest(it) }
}
fun readNext(): Command? {
return commandBus.firstOrNull()
}
fun <U : Command> readNext(commandClass: Class<U>): U? {
return commandBus.filterIsInstance(commandClass).firstOrNull()
}
}

View File

@@ -41,7 +41,7 @@ fun Routing.card() {
val eventStream by inject<EventStream<GameId>>()
post<Game.Card.PutCard> {
val card = call.receive<Card.Simple>()
val card = call.receive<Card>()
eventStream.publish(PlayCardEvent(it.card.game.id, card))
call.respondNullable<Any?>(HttpStatusCode.OK, null)
}

View File

@@ -0,0 +1,52 @@
package eventDemo.app.actions
import eventDemo.app.Command
import eventDemo.app.CommandId
import eventDemo.app.CommandStream
import eventDemo.app.PlayCardCommand
import io.ktor.http.HttpStatusCode
import io.ktor.resources.Resource
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.resources.get
import io.ktor.server.resources.post
import io.ktor.server.response.respond
import io.ktor.server.routing.Routing
import kotlinx.serialization.Serializable
import org.koin.ktor.ext.inject
@Serializable
@Resource("/command")
class CommandRoute {
@Resource("send")
class Send(val commandRoute: CommandRoute) {
@Serializable
data class Response(
val id: CommandId,
) {
constructor(command: Command) : this(command.id)
}
}
@Resource("next")
class Next(val commandRoute: CommandRoute)
}
fun Routing.command() {
val commandStream by inject<CommandStream>()
post<CommandRoute.Send> {
val command = call.receive<PlayCardCommand>()
commandStream.sendRequest(command)
call.respond(HttpStatusCode.OK, CommandRoute.Send.Response(command))
}
get<CommandRoute.Next> {
val command = commandStream.readNext()
if (command == null) {
call.response.status(HttpStatusCode.NoContent)
} else {
call.respond(HttpStatusCode.OK, command)
}
}
}

View File

@@ -1,5 +1,6 @@
package eventDemo.plugins
import eventDemo.app.CommandStream
import eventDemo.app.EventStream
import eventDemo.app.GameId
import io.ktor.server.application.Application
@@ -19,4 +20,5 @@ fun Application.configureKoin() {
val appModule =
module {
singleOf<EventStream<GameId>>(::EventStream)
singleOf<CommandStream>(::CommandStream)
}

View File

@@ -1,6 +1,7 @@
package eventDemo.plugins
import eventDemo.app.actions.card
import eventDemo.app.actions.command
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.application.install
@@ -9,7 +10,6 @@ import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.resources.Resources
import io.ktor.server.response.respondText
import io.ktor.server.routing.routing
import io.ktor.util.converters.DataConversion.Configuration
fun Application.configureRouting() {
install(AutoHeadResponse)
@@ -25,5 +25,6 @@ fun Application.configureRouting() {
routing {
card()
command()
}
}

View File

@@ -1,5 +1,6 @@
package eventDemo.plugins
import eventDemo.app.CommandId
import eventDemo.app.GameId
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
@@ -28,6 +29,19 @@ fun Application.configureSerialization() {
}
}
object CommandIdSerializer : KSerializer<CommandId> {
override fun deserialize(decoder: Decoder): CommandId = CommandId(decoder.decodeString())
override fun serialize(
encoder: Encoder,
value: CommandId,
) {
encoder.encodeString(value.toString())
}
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("CommandId", PrimitiveKind.STRING)
}
object GameIdSerializer : KSerializer<GameId> {
override fun deserialize(decoder: Decoder): GameId = GameId(UUID.fromString(decoder.decodeString()))