Skip to content

Instantly share code, notes, and snippets.

@MateuszNaKodach
Last active June 30, 2025 09:28
Show Gist options
  • Select an option

  • Save MateuszNaKodach/f29cd3eabd063db665d0d6f22cb80213 to your computer and use it in GitHub Desktop.

Select an option

Save MateuszNaKodach/f29cd3eabd063db665d0d6f22cb80213 to your computer and use it in GitHub Desktop.
Vertical Slice implemented in Axon Framework 5 (pure functional domain)
package com.dddheroes.heroesofddd.creaturerecruitment.write
import com.dddheroes.heroesofddd.EventTags
import com.dddheroes.heroesofddd.creaturerecruitment.events.DwellingBuilt
import com.dddheroes.heroesofddd.creaturerecruitment.events.DwellingEvent
import com.dddheroes.heroesofddd.shared.domain.HeroesEvent
import com.dddheroes.heroesofddd.shared.restapi.Headers
import org.axonframework.commandhandling.annotation.CommandHandler
import org.axonframework.commandhandling.gateway.CommandGateway
import org.axonframework.eventhandling.gateway.EventAppender
import org.axonframework.eventsourcing.EventSourcingHandler
import org.axonframework.eventsourcing.annotation.EventSourcedEntity
import org.axonframework.eventsourcing.annotation.reflection.EntityCreator
import org.axonframework.eventsourcing.configuration.EventSourcedEntityModule
import org.axonframework.modelling.annotation.InjectEntity
import org.axonframework.modelling.configuration.StatefulCommandHandlingModule
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.bind.annotation.*
////////////////////////////////////////////
////////// Domain
///////////////////////////////////////////
data class BuildDwelling(
val dwellingId: String,
val creatureId: String,
val costPerTroop: Map<String, Int>,
)
sealed interface DwellingEvent : HeroesEvent {
val dwellingId: String
}
data class DwellingBuilt(
@EventTag(EventTags.DWELLING_ID)
override val dwellingId: String,
val creatureId: String,
val costPerTroop: Map<String, Int>
) : DwellingEvent
private data class State(val isBuilt: Boolean)
private val initialState = State(isBuilt = false)
private fun decide(command: BuildDwelling, state: State): List<DwellingEvent> {
if (state.isBuilt) {
return emptyList()
}
return listOf(
DwellingBuilt(
dwellingId = command.dwellingId,
creatureId = command.creatureId,
costPerTroop = command.costPerTroop
)
)
}
private fun evolve(state: State, event: DwellingEvent): State {
return when (event) {
is DwellingBuilt -> state.copy(isBuilt = true)
else -> state
}
}
////////////////////////////////////////////
////////// Application
///////////////////////////////////////////
@EventSourcedEntity(tagKey = EventTags.DWELLING_ID)
private class EventSourcedState private constructor(val state: State) {
@EntityCreator
constructor() : this(initialState)
@EventSourcingHandler
fun evolve(event: DwellingEvent) = EventSourcedState(evolve(state, event))
}
private class BuildDwellingCommandHandler {
@CommandHandler
fun handle(
command: BuildDwelling,
@InjectEntity(idProperty = EventTags.DWELLING_ID) eventSourced: EventSourcedState,
eventAppender: EventAppender
) {
val events = decide(command, eventSourced.state)
eventAppender.append(events)
}
}
@Configuration
private class BuildDwellingWriteSliceConfig {
@Bean
fun module(): StatefulCommandHandlingModule {
val state = EventSourcedEntityModule.annotated(
String::class.java,
EventSourcedState::class.java
)
return StatefulCommandHandlingModule.named(BuildDwelling::class.simpleName)
.entities()
.entity(state)
.commandHandlers()
.annotatedCommandHandlingComponent { BuildDwellingCommandHandler() }
.build()
}
}
////////////////////////////////////////////
////////// Presentation
///////////////////////////////////////////
@RestController
@RequestMapping("games/{gameId}")
private class BuildDwellingRestApi(private val commandGateway: CommandGateway) {
@JvmRecord
data class Body(val creatureId: String, val costPerTroop: MutableMap<String, Int>)
@PutMapping("/dwellings/{dwellingId}")
fun putDwellings(
@RequestHeader(Headers.PLAYER_ID) playerId: String,
@PathVariable gameId: String,
@PathVariable dwellingId: String,
@RequestBody requestBody: Body
) {
val command =
BuildDwelling(
dwellingId,
requestBody.creatureId,
requestBody.costPerTroop
)
commandGateway.sendAndWait(command)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment