- Published on
Brewing Real-time Notifications: A Magician Guide to Spring Boot, Kafka, ReactJS, and Docker
- Authors
- Name
- Gary Huynh
- @huynhthienthach
Why hello there fellow Java
enthusiast! Ready to dive into a mad jumble of code and coffee? Excellent! Make sure to don your coding helmets (it's a thing, trust me!) and remember - we're about to wrangle some Kafka
and Spring Boot
to implement a killer notification feature
for your web and mobile clients. So let's get those creative Java
juices flowing!
First things first, let's talk about what we're dealing with here: Kafka
and Spring Boot
.
Kafka is a bit like a super-efficient postman, who's guzzling energy drinks and always on the move. It's a distributed streaming platform that allows apps to publish and subscribe to streams of records. Spring Boot, on the other hand, is like your trusty toolbox - a comfy zone filled with everything you need for application development.
Step 1: Conjure a new Spring Boot Project with Spring Initializr
Spring Initializr
is like a magic potion for creating Spring Boot
projects. It's the secret recipe of seasoned Java
sorcerers!
- Zip on over to Spring Initializr.
- Fill out the
Group
andArtifact
details to your heart's content. - Choose your dependencies like a pro - for this mission, we'll need
Spring Web
,Spring for Apache Kafka
, andSpring WebSocket
. - Click 'Generate' with a flourish, and poof! Your zipped project is ready for download.
- Once downloaded, unzip it and open it in your IDE. I'm going to use
IntelliJ IDEA
, because, well, it's the IDE of champions!
Step 2: The Kafka Potion - Producer and Consumer Configuration
Now that we have our Spring Boot
project, let's bring Kafka
into the mix.
Kafka Producer Configuration
is the wizardry that sends our notifications. Here's the code to whip up this concoction:
@Configuration
public class KafkaProducerConfig {
@Value("${kafka.bootstrapAddress}")
private String bootstrapAddress;
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
- Let's create a
NotificationService
that uses thisKafka producer
to send the notification:
@Service
public class NotificationService {
@Autowired
private KafkaTemplate<String,String> kafkaTemplate;
public void sendNotification(String message){
kafkaTemplate.send("notifications", message);
}
}
- But who will listen to these messages? Enter
Kafka Consumer
Configuration:
@Configuration
public class KafkaConsumerConfig {
@Value("${kafka.bootstrapAddress}")
private String bootstrapAddress;
@Value("${kafka.groupId}")
private String groupId;
@Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(configProps);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
- Now, create a
NotificationConsumerService
thatlistens
to the messages and thensends
them to our clients:
@Service
public class NotificationConsumerService {
@Autowired
private SimpMessagingTemplate template;
@K
afkaListener(topics = "notifications", groupId = "group_id")
public void consume(String message){
template.convertAndSend("/topic/notifications", message);
}
}
Step 3: Brewing the React.js Frontend Potion
This one's for our web clients. Create a new React.js
application and add this NotificationComponent
to see the notifications on the webpage. It uses a WebSocket
to listen to notifications from our server and updates the UI whenever it receives a notification.
import React, { Component } from 'react';
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';
class NotificationComponent extends Component {
constructor(props) {
super(props);
this.state = { notifications: [] };
}
componentDidMount() {
this.connect();
}
connect = () => {
const socket = new SockJS('http://localhost:8080/ws');
const stompClient = Stomp.over(socket);
stompClient.connect({}, this.onConnected, this.onError);
};
onConnected = () => {
this.stompClient.subscribe('/topic/notifications', this.onMessageReceived);
};
onMessageReceived = (payload) => {
let notification = JSON.parse(payload.body);
this.setState((prevState) => ({
notifications: [...prevState.notifications, notification],
}));
};
onError = (error) => {
console.log("Could not connect you to the server. Please, try again later!");
};
render() {
return (
<div className="notificationComponent">
{this.state.notifications.map((notification, i) => (
<div key={i} className="notification">
{notification}
</div>
))}
</div>
);
}
}
export default NotificationComponent;
Step 4: Bottle Up Your App With Docker
Now that our potions are all brewed, let's bottle them up.
- Create a
Dockerfile
in the root of your Spring Boot project:
FROM openjdk:11
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
- Build your
Docker image
:
docker build -t my-spring-boot-app .
- Next, create a
Dockerfile
for yourReact.js
application:
FROM node:14 as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:stable-alpine
COPY /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
- Build this Docker image:
docker build -t my-react-app .
- Now that you have your Docker images, you can use
Docker Compose
to start your whole stack:
docker-compose up
And voila! You've just weaved together Spring Boot
, Kafka
, React.js
, and Docker
to create a real-time notification feature
. Your Java
mastery knows no bounds! May the Force of Java be with you, always!