Here is a very rough minimal example with Spring WebSocket + STOMP.
@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfig : WebSocketMessageBrokerConfigurer {
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/ws").setAllowedOriginPatterns("*")
}
override fun configureMessageBroker(registry: MessageBrokerRegistry) {
registry.enableSimpleBroker("/topic", "/queue")
registry.setApplicationDestinationPrefixes("/app")
}
}
data class SubmitMoveMessage(
val matchId: String,
val expectedVersion: Long,
val proposedBoardGroups: List<BoardGroupDto>,
val proposedRackTiles: List<TileDto>
)
data class MatchStateUpdatedEvent(
val matchId: String,
val version: Long,
val boardGroups: List<BoardGroupDto>,
val currentPlayerId: String
)
@Controller
class MatchSocketController(
private val matchService: MatchService,
private val messagingTemplate: SimpMessagingTemplate
) {
@MessageMapping("/matches/{matchId}/move")
fun submitMove(
@DestinationVariable matchId: String,
message: SubmitMoveMessage
) {
val updatedState = matchService.submitMove(matchId, message)
messagingTemplate.convertAndSend(
"/topic/matches/$matchId",
MatchStateUpdatedEvent(
matchId = updatedState.matchId,
version = updatedState.version,
boardGroups = updatedState.boardGroups,
currentPlayerId = updatedState.currentPlayerId
)
)
}
}
@Service
class MatchService {
fun submitMove(matchId: String, message: SubmitMoveMessage): MatchState {
// load current match
// validate move
// apply move
// persist new state
return MatchState(matchId, 2, emptyList(), "player-2")
}
}
/ws/topic/matches/{matchId}/app/matches/{matchId}/moveThat is the basic pattern: