All That Dev

Imperative vs. Reactive API in Spring Boot

September 19, 2024 | by Cícero Fabio

allthaedev.com.backgroud

In Spring Boot, APIs can be built using two main programming paradigms: Imperative and Reactive. While both approaches can achieve similar goals, the way they handle execution, data flow, and system resources differs significantly.

Imperative API

The imperative model is synchronous and blocking. When a request is made, the thread that handles the request waits for the task to complete before moving on to the next task. This is the traditional way of writing Java applications. It’s easier to reason about but can lead to thread exhaustion under high load.

Key Features:

  • Synchronous: Operations are performed in sequence.
  • Blocking: Threads wait for the current task to complete.
  • Thread-per-request: Each request occupies a thread until completion.
  • Simpler: Easier to debug and understand.

Reactive API

The reactive model, on the other hand, is asynchronous and non-blocking. Spring WebFlux, introduced in Spring 5, provides tools to build reactive APIs using the reactive streams specification. Reactive APIs are highly scalable, as they don’t block threads while waiting for resources (e.g., database responses), making them suitable for high-throughput, low-latency applications.

Key Features:

  • Asynchronous: Operations are performed independently of the main thread.
  • Non-blocking: Threads are not held up while waiting for external resources.
  • Event-driven: Built around the concept of events (data streams).
  • Scalable: Better performance under high load.

Example: Building a Reactive API with Spring WebFlux and MongoDB

Below is an example of a Reactive API using Spring WebFlux and MongoDB. It demonstrates how to create a repository, service, and controller using reactive programming principles.

1. Dependency Configuration (pom.xml)

Ensure the following dependencies are included in your pom.xml:

<dependencies>
    <!-- Spring Boot WebFlux -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    <!-- Spring Data Reactive MongoDB -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
    </dependency>
</dependencies>

2. Reactive MongoDB Repository

In Spring Data, we define a repository interface for MongoDB. Here, we extend ReactiveMongoRepository, which provides reactive support.

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Mono;

public interface UserRepository extends ReactiveMongoRepository<User, String> {
    Mono<User> findByEmail(String email);
}

3. Service Layer

The service handles business logic and uses the reactive repository to interact with MongoDB. The methods return Mono or Flux to handle asynchronous data streams.

import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public Mono<User> createUser(User user) {
        return userRepository.save(user);
    }

    public Mono<User> getUserByEmail(String email) {
        return userRepository.findByEmail(email);
    }

    public Flux<User> getAllUsers() {
        return userRepository.findAll();
    }

    public Mono<Void> deleteUser(String userId) {
        return userRepository.deleteById(userId);
    }
}

4. Controller Layer

The controller layer exposes endpoints for the API. Using Spring WebFlux, we define routes that return reactive types (Mono or Flux).

import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public Mono<User> createUser(@RequestBody User user) {
        return userService.createUser(user);
    }

    @GetMapping("/{email}")
    public Mono<User> getUserByEmail(@PathVariable String email) {
        return userService.getUserByEmail(email);
    }

    @GetMapping
    public Flux<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @DeleteMapping("/{id}")
    public Mono<Void> deleteUser(@PathVariable String id) {
        return userService.deleteUser(id);
    }
}

5. MongoDB Configuration (Optional)

If MongoDB is not auto-configured, you might need to manually define the connection settings.

import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;

@Configuration
public class MongoConfig extends AbstractReactiveMongoConfiguration {

    @Override
    public MongoClient reactiveMongoClient() {
        return MongoClients.create("mongodb://localhost:27017");
    }

    @Override
    protected String getDatabaseName() {
        return "mydatabase";
    }
}

Conclusion

While imperative APIs are easier to write and reason about, reactive APIs provide significant performance benefits under high load. They can handle thousands of concurrent requests with a small thread pool, making them ideal for modern microservices and distributed systems. By using Spring WebFlux with MongoDB, you can create highly efficient and scalable reactive APIs.

This example demonstrates a basic CRUD setup, but WebFlux can also handle more complex reactive scenarios, such as streaming data, server-sent events, and WebSockets, all with non-blocking, asynchronous execution.

RELATED POSTS

View all

view all