02 / 06 Backend Developer live demo below
Real-Time Collaboration Platform
Developed a robust meeting system using Spring Boot, WebSockets, and WebRTC, enabling real-time chat, audio, and video communication with minimal latency. Architected core microservices like Config Server, Eureka Discovery Server, Gateway Server, and Meet Signalling Server, ensuring scalable, low-latency, and high-performance communication.
01 · The constraint
Real-time meetings break monoliths
A monolith looks fine for the first sprint — auth, chat, presence, and a stubbed audio relay all in one Spring Boot app. The trouble is that 'real-time' has a latency budget the monolith can't share. A slow product-catalog query on the same JVM shows up as a 200 ms hitch in someone's hand-raise.
The deeper issue is operational. Every feature touches the same WAR. Every deploy is a meeting interruption. Scaling the audio relay separately from the user-profile service is impossible while they share a thread pool.
02 · The split
Seven services, one capability each
The platform is seven small Spring Boot apps. Frontend, Gateway, User Service, Product Service, MeetingSignaling, Config Server, Eureka Discovery. Each one boots in seconds, can be rebuilt independently, and uses Eureka to find its neighbours.
The hard part isn't writing seven apps — it's making them coordinate. The next three chapters are the supporting cast: how they discover each other, how external traffic enters, and how the signaling fan-out actually works.
03 · Cross-cutting plumbing
Eureka holds the address book. Config Server holds the secrets.
Every service boots, reads
optional:configserver:http://localhost:8888from itsapplication.yml, and pulls its environment from the Config Server. No hard-coded hostnames, no per-environment WARs.After config, each service registers with Eureka at
:8761. From that point the Gateway can route tosignalingby name instead of by host:port, and replacing the signaling service's machine is just a restart away from the rest of the cluster knowing about it.●MeetingSignalingService/src/main/resources/application.ymlyaml 1spring:2 application:3 name: signaling4 config:5 import: optional:configserver:http://localhost:88886eureka:7 client:8 service-url:9 defaultZone: http://localhost:8761/eureka10server:11 port: 809104 · The edge
Spring Cloud Gateway · the only public port
Everything outside the cluster talks to one place:
:8222, the Gateway. The Gateway uses Spring Cloud's predicate-route DSL — match a path prefix, forward to a service. No service besides the Gateway has its port exposed.This also makes auth one-shot. The Gateway runs the token check; the downstream services trust the gateway to have done so and read the user out of forwarded headers.
●Gateway/src/main/resources/application.ymlyaml 1cloud:2 gateway:3 routes:4 - id: user5 uri: http://localhost:80906 predicates:7 - Path=/api/v1/users/**8 - id: frontend9 uri: http://localhost:809210 predicates:11 - Path=/meet/**05 · The hot loop
STOMP over WebSocket · six signal types, one topic per room
The signaling service is where every meeting lives. Each browser opens a STOMP subscription to
/topic/room/{roomId}and publishes to/app/sendSignal. The controller dispatches bySignalType—JOIN,LEAVE,CHAT,MIC,VIDEO,HAND_RAISE— and re-emits onto the topic so every other peer in the room gets the update.The fan-out is the broker's job, not Spring's. The service stays stateless: any instance can serve any room, the broker handles the rest.
●MeetingSignalingService/.../SignalingController.javajava 1@MessageMapping("/sendSignal")2@SendTo("/topic/room/roomId")3public SignalingMessage handle(SignalingMessage m,4 SimpMessageHeaderAccessor h) {5 switch (m.getSignalType()) {6 case JOIN -> signalingService.addUserToRoom(m, h);7 case LEAVE -> signalingService.removeUserFromRoom(m, h);8 case CHAT -> signalingService.broadcastChatMessage(m, h);9 case MIC -> signalingService.broadcastMicStatusUpdate(m, h);10 case VIDEO -> signalingService.broadcastVideoStatusUpdate(m, h);11 case HAND_RAISE -> signalingService.broadcastHandRaise(m, h);12 }13 return m;14}06 · Why this scales
Media bytes never touch our servers
Audio and video go peer-to-peer via WebRTC. The signaling service helps two browsers find each other —
JOINtells the other peers about a newcomer, the signaling channel carries the SDP offer/answer plus ICE candidates — and then steps out of the way.Server-side cost per meeting is just the WebSocket fan-out for the six signal types above. The bandwidth-heavy part of a call (video) is bilateral between peers. Doubling meeting count doubles socket count, not bandwidth.
07 · Try it
Open a room. Share the link. Anyone joins.
The widget below is a real
RTCPeerConnectionnegotiated in your browser. The signaling backend is/api/webrtc/signal— Upstash Redis if configured, in-memory otherwise — running the same hello → offer → answer → ICE dance the production service runs over STOMP. Open the share URL on another device and the two browsers pair directly; media never touches our server.
01 · The constraint
Real-time meetings break monoliths
A monolith looks fine for the first sprint — auth, chat, presence, and a stubbed audio relay all in one Spring Boot app. The trouble is that 'real-time' has a latency budget the monolith can't share. A slow product-catalog query on the same JVM shows up as a 200 ms hitch in someone's hand-raise.
The deeper issue is operational. Every feature touches the same WAR. Every deploy is a meeting interruption. Scaling the audio relay separately from the user-profile service is impossible while they share a thread pool.
02 · The split
Seven services, one capability each
The platform is seven small Spring Boot apps. Frontend, Gateway, User Service, Product Service, MeetingSignaling, Config Server, Eureka Discovery. Each one boots in seconds, can be rebuilt independently, and uses Eureka to find its neighbours.
The hard part isn't writing seven apps — it's making them coordinate. The next three chapters are the supporting cast: how they discover each other, how external traffic enters, and how the signaling fan-out actually works.
03 · Cross-cutting plumbing
Eureka holds the address book. Config Server holds the secrets.
Every service boots, reads
optional:configserver:http://localhost:8888from itsapplication.yml, and pulls its environment from the Config Server. No hard-coded hostnames, no per-environment WARs.After config, each service registers with Eureka at
:8761. From that point the Gateway can route tosignalingby name instead of by host:port, and replacing the signaling service's machine is just a restart away from the rest of the cluster knowing about it.●MeetingSignalingService/src/main/resources/application.ymlyaml 1spring:2 application:3 name: signaling4 config:5 import: optional:configserver:http://localhost:88886eureka:7 client:8 service-url:9 defaultZone: http://localhost:8761/eureka10server:11 port: 809104 · The edge
Spring Cloud Gateway · the only public port
Everything outside the cluster talks to one place:
:8222, the Gateway. The Gateway uses Spring Cloud's predicate-route DSL — match a path prefix, forward to a service. No service besides the Gateway has its port exposed.This also makes auth one-shot. The Gateway runs the token check; the downstream services trust the gateway to have done so and read the user out of forwarded headers.
●Gateway/src/main/resources/application.ymlyaml 1cloud:2 gateway:3 routes:4 - id: user5 uri: http://localhost:80906 predicates:7 - Path=/api/v1/users/**8 - id: frontend9 uri: http://localhost:809210 predicates:11 - Path=/meet/**05 · The hot loop
STOMP over WebSocket · six signal types, one topic per room
The signaling service is where every meeting lives. Each browser opens a STOMP subscription to
/topic/room/{roomId}and publishes to/app/sendSignal. The controller dispatches bySignalType—JOIN,LEAVE,CHAT,MIC,VIDEO,HAND_RAISE— and re-emits onto the topic so every other peer in the room gets the update.The fan-out is the broker's job, not Spring's. The service stays stateless: any instance can serve any room, the broker handles the rest.
●MeetingSignalingService/.../SignalingController.javajava 1@MessageMapping("/sendSignal")2@SendTo("/topic/room/roomId")3public SignalingMessage handle(SignalingMessage m,4 SimpMessageHeaderAccessor h) {5 switch (m.getSignalType()) {6 case JOIN -> signalingService.addUserToRoom(m, h);7 case LEAVE -> signalingService.removeUserFromRoom(m, h);8 case CHAT -> signalingService.broadcastChatMessage(m, h);9 case MIC -> signalingService.broadcastMicStatusUpdate(m, h);10 case VIDEO -> signalingService.broadcastVideoStatusUpdate(m, h);11 case HAND_RAISE -> signalingService.broadcastHandRaise(m, h);12 }13 return m;14}06 · Why this scales
Media bytes never touch our servers
Audio and video go peer-to-peer via WebRTC. The signaling service helps two browsers find each other —
JOINtells the other peers about a newcomer, the signaling channel carries the SDP offer/answer plus ICE candidates — and then steps out of the way.Server-side cost per meeting is just the WebSocket fan-out for the six signal types above. The bandwidth-heavy part of a call (video) is bilateral between peers. Doubling meeting count doubles socket count, not bandwidth.
07 · Try it
Open a room. Share the link. Anyone joins.
The widget below is a real
RTCPeerConnectionnegotiated in your browser. The signaling backend is/api/webrtc/signal— Upstash Redis if configured, in-memory otherwise — running the same hello → offer → answer → ICE dance the production service runs over STOMP. Open the share URL on another device and the two browsers pair directly; media never touches our server.
Built with.
Senior backend and platform roles, hard systems problems, or an argument about thread pools.
Open a connectionRecruiter mode