08/05/2026 06:51am

Golang The Series EP 131: Integrating WebSockets into Microservices
#Go
#Golang
#WebSocket
#Microservices
#Microservices Architecture
Welcome back, Gophers! Building a standalone WebSocket server is a great start, but making it play nice with dozens of other microservices is where the real engineering begins.
In an enterprise-grade microservices architecture, your WebSocket service is no longer an isolated component. It evolves into a "Real-time Gateway" or "Notification Hub"—a specialized bridge that pushes critical updates from internal services directly to the user's screen in milliseconds.
1. Service Topology: Where Does the WebSocket Live?
When moving to microservices, you face a critical design choice regarding where to place your WebSocket logic:
- Dedicated Notification Service (Recommended): A specialized service solely responsible for managing persistent TCP connections.
- Pros: Allows for independent scaling. Since WebSockets are Stateful and consume significant memory per connection, separating them prevents them from starving your stateless REST APIs of resources.
- Cons: Requires additional network routing and a robust internal communication strategy.
- Embedded (Internal to Business Services): For example, placing WebSocket logic directly inside the chat-service.
- Pros: Simpler to develop initially as the logic and connection live in the same codebase.
- Cons: Difficult to scale. High connection counts can degrade the performance of core business logic.
Pro Tip: For production systems, go with a Dedicated Service. Resource Isolation is key to maintaining a stable and predictable system.
2. Solving the Authentication Puzzle via API Gateway
A common headache for developers is that the standard browser-side new WebSocket() API does not support custom headers. This means you cannot send Authorization: Bearer <JWT> during the initial handshake.
The Industry Standard: Ticket-based Authentication
- Request a Ticket: The client makes a standard REST API call (with headers) to your Auth Service to request a "One-time Ticket."
- Issue Ticket: The server generates a short-lived (30–60 seconds) random string, stores it in Redis, and returns it to the client.
- Connect: The client initiates the WebSocket connection, passing the ticket via a query string: ws://api.example.com/ws?ticket=XYZ.
- Validate: The WebSocket service checks the ticket against Redis. If valid, the connection is upgraded; if not, it's rejected.
3. Communication Patterns: How Other Services Talk to WebSockets
When the Order Service completes a transaction, how does it tell the WebSocket Service to notify the user?
The Standard Pattern: Use a Message Broker. The Order Service simply publishes an event like "Order #123 Completed" to a topic. All WebSocket Service instances subscribe to this topic. The instance holding that specific user's connection then delivers the message (as we implemented in EP 130).
4. Implementation: Using gRPC for Internal Messaging
In Go, we prefer gRPC for internal communication due to its high performance and type safety. Here is how your WebSocket service might receive a push request from another service:
Go
// Notification service implementation receiving gRPC calls
type notificationServer struct {
pb.UnimplementedNotificationServer
hub *Hub // Our Hub from EP 130
}
func (s *notificationServer) PushNotification(ctx context.Context, req *pb.PushRequest) (*pb.PushResponse, error) {
// 1. Check if the user is connected to THIS specific instance
if client, ok := s.hub.GetLocalClient(req.UserId); ok {
client.send <- []byte(req.Message)
return &pb.PushResponse{Status: "Delivered_Locally"}, nil
}
// 2. If not found locally, publish to Redis Pub/Sub so other instances can handle it
err := s.hub.redisClient.Publish(ctx, "global_notifications", req.Message).Err()
if err != nil {
return nil, status.Errorf(codes.Internal, "Broadcast failed: %v", err)
}
return &pb.PushResponse{Status: "Dispatched_to_Cluster"}, nil
}
5. Infrastructure: Service Discovery & Load Balancing
- Service Discovery: Use tools like Consul or Kubernetes Services. In K8s, a "Headless Service" is often useful for allowing services to discover individual WebSocket pods.
- Layer 7 Load Balancing: Ensure your ingress (like Nginx or Traefik) is configured to handle the Upgrade protocol.
- Sticky Sessions: While we have a backplane (Redis), Session Affinity is still beneficial. It reduces the overhead of constant re-handshaking during rolling updates.
- Service Mesh Pitfall: If using Istio or Linkerd, check your idle timeouts. Service meshes often have aggressive default timeouts that might drop your idle WebSocket connections prematurely.
Summary
Integrating WebSockets into Microservices is less about "making a connection" and more about "orchestrating data flow." By utilizing Ticket-based Auth and a Message Broker backplane, you ensure your real-time system is secure, decoupled, and ready to scale.
In the Next Episode (EP 132): A must-read for production teams—Cloud Cost Optimization for WebSocket Systems. How do we maintain high-performance real-time systems without breaking the bank? See you then!