Implementing Real-Time Communication In Restaurant Backend With WebSockets
User Story
As a chef, I want to see new orders arrive on my screen in real time. This user story underscores the need for immediate updates in a fast-paced environment like a restaurant kitchen. Real-time notifications ensure that chefs can start preparing orders without delay, reducing wait times and improving customer satisfaction. To achieve this, we'll leverage the power of WebSockets, a communication protocol that enables bidirectional, full-duplex communication over a single TCP connection.
Tasks
To implement real-time order notifications, we need to perform several tasks:
- Add the
spring-boot-starter-websocket
dependency. - Configure a
WebSocketMessageBroker
(STOMP over WebSocket). - Define communication topics (e.g.,
/topic/kitchen/new-orders
). - Modify the
OrderService
to push a message to the WebSocket topic when a newOrderItem
is added. - Secure WebSocket access using JWT tokens.
Step 1: Adding the WebSocket Dependency
The first step is to include the necessary dependency in our Spring Boot project. The spring-boot-starter-websocket
dependency provides the core functionalities for working with WebSockets. To add this dependency, include the following in your pom.xml
file if you're using Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
If you are using Gradle, add the following to your build.gradle
file:
implementation 'org.springframework.boot:spring-boot-starter-websocket'
This dependency pulls in all the required libraries for WebSocket support, making it easier to configure and use WebSockets in our application. Including this essential dependency sets the stage for building real-time features.
Step 2: Configuring the WebSocket Message Broker
The WebSocketMessageBroker
is a crucial component that handles the routing of messages between clients and the server. We'll use STOMP (Simple Text Oriented Messaging Protocol) over WebSocket, which provides a simple and interoperable way to send and receive messages. To configure the message broker, we need to create a configuration class that extends AbstractWebSocketMessageBrokerConfigurer
. This class allows us to define the endpoints and message brokers required for our application.
Here’s an example of how to configure the WebSocketMessageBroker
:
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
}
In this configuration:
@EnableWebSocketMessageBroker
enables WebSocket message handling, backed by a message broker.configureMessageBroker
configures the message broker:enableSimpleBroker("/topic")
enables a simple in-memory broker for destinations prefixed with/topic
.setApplicationDestinationPrefixes("/app")
sets the prefix for messages that are routed to controller methods.
registerStompEndpoints
registers the/ws
endpoint, which clients will use to connect to the WebSocket. ThewithSockJS()
method enables fallback options for browsers that do not support WebSockets.
This configuration is fundamental for setting up the WebSocket infrastructure. The message broker ensures that messages are correctly routed to the intended recipients, and the endpoint allows clients to establish a WebSocket connection.
Step 3: Defining Communication Topics
Topics are channels through which messages are routed. In our restaurant backend, we need to define topics that represent specific events or areas of interest. For example, we can define a topic for new orders in the kitchen (/topic/kitchen/new-orders
) and another for updates on order status (/topic/order/status
). Defining clear and specific topics helps in organizing the message flow and ensures that clients receive only the information they need.
Here are a few examples of topics we might use:
/topic/kitchen/new-orders
: For notifying the kitchen about new orders./topic/cashier/new-transactions
: For informing the cashier about new transactions./topic/order/status
: For updating clients on the status of their orders.
When choosing topic names, it’s important to follow a consistent naming convention. Using a hierarchical structure (e.g., /topic/area/event
) makes it easier to manage and understand the topics. This structured approach is crucial for maintaining a scalable and organized real-time communication system.
Step 4: Modifying the OrderService to Push Messages
Now, we need to modify the OrderService
to publish a message to the appropriate WebSocket topic whenever a new OrderItem
is added. This involves injecting a SimpMessagingTemplate
into the OrderService
and using it to send messages. The SimpMessagingTemplate
is a Spring class that provides a convenient way to send messages to STOMP destinations.
Here’s how you can modify the OrderService
:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private SimpMessagingTemplate messagingTemplate;
public void addOrderItem(OrderItem orderItem) {
// Save the order item to the database
// ...
// Notify the kitchen about the new order
messagingTemplate.convertAndSend("/topic/kitchen/new-orders", orderItem);
}
}
In this code:
- We inject
SimpMessagingTemplate
into theOrderService
. - In the
addOrderItem
method, after saving the order item, we usemessagingTemplate.convertAndSend
to send a message to the/topic/kitchen/new-orders
topic. TheorderItem
object is sent as the payload of the message. This ensures that every time a new order item is added, the kitchen is notified in real-time. This critical step connects the backend logic with the real-time communication system.
Step 5: Securing WebSocket Access with JWT
Security is paramount, and we need to ensure that only authenticated users can access our WebSocket endpoints. To achieve this, we'll secure WebSocket access using JSON Web Tokens (JWT). JWT is a standard for securely transmitting information as a JSON object. We can use JWT to authenticate users and authorize their access to WebSocket topics. Securing WebSocket access is essential to prevent unauthorized access and ensure data integrity.
To secure the WebSockets with JWT, you'll typically need to:
- Configure Spring Security to validate JWT tokens.
- Implement a
ChannelInterceptor
to intercept WebSocket messages and authenticate users based on the JWT token.
Here’s a basic outline of how you might implement this:
1. Configure Spring Security
You'll need to set up Spring Security to validate the JWT tokens sent by the client. This usually involves creating a JwtAuthenticationFilter
that extracts the token from the header, validates it, and sets the authentication in the Spring Security context.
2. Implement a ChannelInterceptor
A ChannelInterceptor
allows you to intercept messages before they are sent to the message broker. We can use this to authenticate users based on their JWT tokens.
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Component
public class WebSocketAuthInterceptor implements ChannelInterceptor {
// Your JWT validation logic here
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (accessor.getCommand() == StompCommand.CONNECT) {
String token = accessor.getFirstNativeHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
// Validate the token and get user details
UserDetails userDetails = validateTokenAndGetUserDetails(token);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
accessor.setUser(authentication);
}
}
}
return message;
}
}
This interceptor checks for an Authorization
header in the CONNECT message, validates the JWT token, and sets the authentication in the Spring Security context. This ensures that only users with valid tokens can establish a WebSocket connection. Implementing this level of security is vital for protecting sensitive data.
3. Configure the WebSocket Message Broker to use the Interceptor
Finally, you need to configure the WebSocketMessageBroker
to use your ChannelInterceptor
.
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Autowired
private WebSocketAuthInterceptor authInterceptor;
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(authInterceptor);
}
// Other configurations
}
By adding the interceptor, you ensure that every incoming message is checked for authentication, providing a secure WebSocket communication channel. This comprehensive security setup is crucial for production environments.
Conclusion
Implementing real-time communication using WebSockets can significantly enhance the efficiency and responsiveness of a restaurant backend. By following these steps, you can enable chefs to receive instant order notifications, cashiers to process transactions seamlessly, and keep everyone informed in real-time. Securing your WebSockets with JWT ensures that this communication remains protected and reliable. This comprehensive approach not only improves operations but also enhances the overall customer experience. Real-time communication is a cornerstone of modern applications, and WebSockets provide a powerful and efficient way to achieve it.