Full source code: examples-seonwkim/spring-boot-chat
Actor library: seonWKim/spring-boot-starter-actor
π€ Why Actors?
Most distributed chat systems rely on middleware like Redis Pub/Sub or Kafka to deliver messages between users on different servers. But there's an alternative: clustering servers with a distributed actor system.
In this post, Iβll walk you through building a fully functional distributed chat app using:
- π§© Pekko (Akkaβs Apache fork)
- βοΈ Spring Boot 3
- π WebSocket + WebFlux
- π¬ Actors for session & room handling
π¦ Setup: spring-boot-starter-actor
To reduce integration complexity, I created spring-boot-starter-actor
, a library that brings native actor system support into Spring Boot.
Key dependencies:
dependencyManagement {
imports {
// pekko-serialization-jackson_3 require minimum 2.17.3 version of jackson
mavenBom("com.fasterxml.jackson:jackson-bom:2.17.3")
}
}
dependencies {
implementation("io.github.seonwkim:spring-boot-starter-actor_3:0.0.25")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
}
π§ Architecture Overview
The application is structured around two core actor types:
UserActor β ChatRoomActor
- UserActor β Handles one user's session.
- ChatRoomActor β Represents a unique room across the cluster.
- WebSocketHandler β Bridges WebSocket and actor system.
Each user is represented by an actor that joins/leaves rooms and sends/receives messages. The room actor manages broadcasting to all users.
π¬ WebSocket Setup
Create a WebSocket endpoint via Spring config:
@Configuration
@EnableWebSocket
class WebSocketConfig(
private val chatWebSocketHandler: ChatWebSocketHandler
) : WebSocketConfigurer {
override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
registry.addHandler(chatWebSocketHandler, "/ws/chat").setAllowedOrigins("*")
}
}
This sets up /ws/chat
as your chat entrypoint.
π§βπ» UserActor (One per connected user)
Each connected WebSocket user is handled by their own actor:
class UserActor : SpringActor {
data class JoinRoom(val roomId: String) : Command
data class SendMessage(val message: String) : Command
...
}
Behavior:
- On connect β Sends
connected
event - On
JoinRoom
β Registers with theChatRoomActor
- On message β Relays to
ChatRoomActor
- On cluster event β Receives
JoinRoomEvent
,LeaveRoomEvent
,SendMessageEvent
π ChatRoomActor (Cluster-sharded)
Rooms are unique across the cluster thanks to Pekko's Cluster Sharding:
class ChatRoomActor : ShardedActor<ChatRoomActor.Command> {
data class JoinRoom(val userId: String, val userRef: ActorRef<UserActor.Command>) : Command
data class SendMessage(val userId: String, val message: String) : Command
...
}
Each room:
- Tracks active users
- Broadcasts join/leave/message events
- Lives on a node chosen by Pekkoβs sharding strategy
π WebSocket Handler
Bridges incoming WebSocket traffic to actors:
override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
when (payload.get("type").asText()) {
"join" -> userActor.tell(JoinRoom(roomId))
"message" -> userActor.tell(SendMessage(message))
}
}
Also handles connection setup/teardown and assigns userIds.
π Running Multiple Nodes (Clustering)
Spin up a 3-node cluster with one script:
sh cluster-start.sh 8080 2551 3
Each instance gets a unique port and Pekko cluster port. Open localhost:8080
, 8081
, and 8082
in different tabsβvoilΓ , distributed chat!
β Why This Rocks
- β No Redis/Kafka/3rd-party message queue
- β Horizontal scalability with Pekko sharding
- π§ Stateful logic using just actors
- πΈ Seamless WebSocket session per user
- π― Built with familiar Spring Boot ergonomics
π Try it Yourself
- Chat App: examples-seonwkim/spring-boot-chat
- Actor Integration: seonWKim/spring-boot-starter-actor
π§ Next Steps
- Make the actor library production-grade (
v1.0.0
) - Add observability and failover strategies
- Integrate persistent actors for message durability
π¬ Feedback, questions, contributions β all welcome!
Let me know if you'd like a follow-up post diving into actor design patterns or cluster internals.
Top comments (0)