<link rel="stylesheet" href="styles-3GBANQL6.css">
← Back to blog

Spring Boot

Spring Boot Microservices with Kafka and PostgreSQL

Spring Boot Microservices with Kafka and PostgreSQL

Introduction

In modern software development, microservices architecture has become increasingly popular due to its scalability, flexibility, and ease of maintenance. Combining Spring Boot microservices with Kafka for messaging and PostgreSQL for data persistence provides a robust solution for building distributed systems. This article will cover the necessary configurations, real-world examples, and potential pitfalls while offering unit tests and alternative technologies to mitigate those issues.

Why Use Kafka with Spring Boot Microservices?

Apache Kafka is a distributed event streaming platform capable of handling trillions of events a day. It provides:

  • High Throughput: Kafka can handle a large volume of messages with minimal latency.
  • Scalability: Kafka can easily scale horizontally, making it suitable for growing applications.
  • Decoupled Communication: Microservices can communicate asynchronously through Kafka, improving system resilience and decoupling the services.

In a microservices architecture, Kafka serves as a message broker, allowing different services to send and receive messages without needing to know about each other's existence. This leads to more maintainable and flexible systems.

Configuring Spring Boot Microservices

1. Setting Up Dependencies

First, you need to add the required dependencies to your pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

2. Configuring PostgreSQL

Add the PostgreSQL configuration to your application.properties file:

spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=myuser
spring.datasource.password=mypassword
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

3. Configuring Kafka

Next, configure Kafka in the same application.properties file:

spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=my-group
spring.kafka.consumer.auto-offset-reset=earliest

Creating the Microservice

1. Entity Class

Create a simple User entity class to represent a user in the system:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2. Repository Interface

Create a repository interface for data access using Spring Data JPA:

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository {
}

3. Kafka Producer Service

Create a service to send messages to Kafka when a new user is created:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class UserProducerService {
    private static final String TOPIC = "users";

    @Autowired
    private KafkaTemplate kafkaTemplate;

    public void sendUser(User user) {
        kafkaTemplate.send(TOPIC, user);
    }
}

4. Kafka Consumer Service

Create a service to consume messages from Kafka. This service processes the user data received from Kafka:

import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

@Service
public class UserConsumerService {
    @KafkaListener(topics = "users", groupId = "my-group")
    public void consume(User user) {
        System.out.println("Consumed user: " + user.getName());
        // Add logic to process the user data
    }
}

5. Controller

Create a REST controller to handle user requests. When a user is created, it will save the user to the database and send a message to Kafka:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserProducerService userProducerService;

    @PostMapping
    public User createUser(@RequestBody User user) {
        User savedUser = userRepository.save(user);
        userProducerService.sendUser(savedUser);
        return savedUser;
    }
}

How Kafka Handles Requests in Microservices Architecture

In a microservices architecture, Kafka acts as a central hub for communication between various services. Here’s how it works:

  1. Publishing Messages: When a service (like UserController) publishes an event (like creating a new user), it sends a message to a specific Kafka topic (like users).
  2. Consuming Messages: Other services (like UserConsumerService) subscribe to this topic and receive the messages asynchronously. This allows services to react to events without direct communication.
  3. Decoupling Services: This decoupling improves system resilience, as services can evolve independently. If a service fails or needs to be updated, it doesn't affect other services as long as the contract (message structure) remains unchanged.

Unit Testing the Microservice

Unit tests are essential for ensuring the reliability of your microservice. Here’s an example of how to test the UserController:

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@WebMvcTest(UserController.class)
public class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Mock
    private UserRepository userRepository;

    @Mock
    private UserProducerService userProducerService;

    @InjectMocks
    private UserController userController;

    @Test
    public void createUser_ShouldReturnCreatedUser() throws Exception {
        User user = new User();
        user.setName("John Doe");

        when(userRepository.save(any(User.class))).thenReturn(user);

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"John Doe\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("John Doe"));
    }
}

Potential Pitfalls and Alternatives

1. Complexity of Microservices

Microservices architecture can lead to increased complexity, especially when managing multiple services. This can result in challenges related to inter-service communication, data consistency, and deployment.

Alternative: Consider using serverless architecture or a monolithic approach for simpler applications, which can reduce complexity.

2. Managing Kafka

Setting up and managing Kafka can be a challenge, particularly concerning broker configurations, topic management, and ensuring high availability.

Alternative: Use managed messaging services like Amazon SQS or Google Pub/Sub, which abstract away the complexities of managing Kafka clusters.

3. Database Performance

As the application scales, PostgreSQL performance might become a bottleneck, especially with high write loads.

Alternative: Consider using NoSQL databases like MongoDB or Cassandra for high-throughput requirements or sharding PostgreSQL for better performance.

Conclusion

Integrating Spring Boot microservices with Kafka and PostgreSQL provides a powerful framework for building scalable applications. By following the configuration guidelines and implementing best practices, you can create a robust system that efficiently handles data and messaging. While there are pitfalls associated with this architecture, understanding these challenges and considering alternative technologies can help you build successful applications.

LET'S BUILD WHAT'S NEXT

Ready to turn your idea into a real solution?

Let's discuss how we can help you create value with technology.

Let's Talk