Traditional vs. Reactive Programming: A Quick Overview

Traditional vs. Reactive Programming: A Quick Overview

Overview

This article introduces reactive programming and compares it with traditional programming. It highlights the benefits of reactive programming, such as improved responsiveness and scalability, through practical examples. Key concepts like Mono and Flux are explained, along with common mistakes to avoid.

Introduction to Reactive Programming

Reactive programming is a paradigm focused on asynchronous data streams and the propagation of changes. It allows applications to handle real-time data and respond to events as they happen, making it ideal for modern applications that require high scalability and responsiveness.

Traditional Programming

In traditional programming, requests are processed synchronously and block the execution thread. Here are the key characteristics:

  • Synchronous Execution: Each request must be completed before the next one starts.
  • Blocking I/O: Operations wait for external resources, which can lead to inefficiencies.
  • Limited Scalability: High user loads can slow down the server and degrade performance.
  • Memory Consumption: Traditional applications often consume more memory due to thread management. Each request typically requires a dedicated thread, leading to high memory usage with many concurrent requests.

Example:

I am using a JAR provided in the GitHub repository to simulate an external system that returns a list of tasks.

Article content
Traditional

  1. RestClient Initialization: A RestClient is created using a builder, with a base URL set to http://localhost:7070. This client is used for making synchronous HTTP requests.
  2. Synchronous Request: Inside the getTasks() method, a GET request is made to the /tasks URI using the restClient.
  3. Response Handling:

retrieve(): Initiates the request and prepares to retrieve the response.

body(new ParameterizedTypeReference<List<TaskDto>>() {}): Converts the response body into a synchronous List<TaskDto>, which will block until the response is fully received.

log.info("received response: {}", list): Logs the received list of TaskDto objects.

The method returns the list of tasks.

This code retrieves a list of tasks from the specified endpoint in a blocking manner, meaning the execution waits until the response is fully received.

Reactive Programming

Reactive programming addresses these limitations by emphasizing non-blocking, asynchronous execution:

  • Asynchronous Execution: Requests are handled without waiting for previous ones to finish.
  • Non-Blocking I/O: Threads remain free while waiting for data, improving resource usage.
  • High Scalability: Better performance under high loads due to efficient resource management.
  • Memory Consumption: Reactive applications are generally more memory-efficient. They utilize a smaller number of threads and leverage callbacks and event loops, which can lead to lower memory overhead even with many concurrent users.

Example:

Article content
Reactive

  1. WebClient Initialization: The WebClient is created using a builder, with a base URL set to http://localhost:7070. This client will be used to make HTTP requests.
  2. Asynchronous Request: Inside the getTasks() method, a GET request is made to the /tasks URI using the webClient.
  3. Response Handling:

retrieve(): Initiates the request and prepares to retrieve the response.

bodyToFlux(TaskDto.class): Converts the response body into a reactive Flux of TaskDto, allowing the handling of multiple items asynchronously.

onErrorComplete(): Ensures that if an error occurs during the data retrieval, the stream will complete gracefully without throwing an error.

doOnNext(p -> log.info("received: {}", p)): Logs each received TaskDto object as it is processed.

This code effectively retrieves a list of tasks from the specified endpoint in a non-blocking manner.

Feature Additions:

  • Immediate Cancellation: In a reactive system, if a request is canceled, the operation is immediately terminated. This contrasts with traditional systems, where cancellation might not be instantaneous due to blocking operations.
  • Error Handling with onErrorComplete: If external systems or data sources fail, adding an onErrorComplete() allowing the application to gracefully handle data until the point of failure.

Understanding Mono and Flux

Mono and Flux are essential components in reactive programming, both implementing the Publisher interface from the Reactive Streams specification. They provide efficient, non-blocking methods for managing data streams:

  • Mono: Publisher of Zero or One Item: Mono emits a single item or none, making it ideal for scenarios where a result may or may not be present, like fetching a single record.

Comparable to Optional: While it shares the "zero or one" nature with Optional in traditional programming, Mono stands out with its asynchronous, non-blocking capabilities.

  • Flux: Publisher of Zero to Many Items: Flux handles streams of multiple items, perfect for operations like retrieving multiple records or processing live data feeds.

Comparable to List: It resembles a List in its ability to manage multiple items, but unlike a List, Flux operates asynchronously, facilitating continuous and dynamic data flows.

While these analogies to Optional and List help in understanding their roles, Mono and Flux are distinct in their focus on asynchronous and reactive data management, enabling scalable and responsive applications.

Common Mistakes:

  • Misunderstanding of Reactive Code: A common mistake is using reactive programming structures in a non-reactive manner.

Article content

  • Explanation: This approach is not truly reactive because it processes the complete response before converting it into a Flux. In a genuine reactive setup, data should be streamed and processed asynchronously as it arrives, not collected in full before processing. This defeats the purpose of reactive programming, which is to handle data streams efficiently and asynchronously.

Conclusion

Reactive programming offers significant advantages over traditional programming in terms of responsiveness, scalability, and memory efficiency. It is particularly suited for applications that require real-time data handling. Understanding these differences can help developers create more efficient and resilient systems.

For practical examples and further exploration of traditional vs. reactive programming, you can check out my repository: taskRep.

For more details on Reactive Programming and how to use Mono and Flux, you can visit the official documentations:

  1. Reactor 3 Reference Guide
  2. Spring WebFlux

If you notice any inaccuracies or would like to discuss further, please feel free to reach out to me.

To view or add a comment, sign in

Insights from the community

Others also viewed

Explore topics