This article explores our decision to move away from reactive architecture in our software project. We'll delve into the core principles of reactive systems, the benefits of non-blocking I/O, and the challenges we faced with a reactive approach.
Reactive encompasses a set of principles and guidelines aimed at constructing responsive distributed systems and applications, characterized by:
One key benefit of reactive systems is their use of non-blocking I/O. This approach avoids blocking threads during I/O operations, allowing a single thread to handle multiple requests concurrently. This can significantly improve system efficiency compared to traditional blocking I/O.
In traditional multithreading, blocking operations pose significant challenges in optimizing systems (Figure 1). Greedy applications consuming excessive memory are inefficient and penalize other applications, often necessitating requests for additional resources like memory, CPU, or larger virtual machines.
Figure 1 – Traditional Multi-threading
I/O operations are integral to modern systems, and efficiently managing them is paramount to prevent greedy behavior. Reactive systems employ non-blocking I/O, enabling a low number of OS threads to handle numerous concurrent I/O operations.
Although non-blocking I/O offers substantial benefits, it introduces a novel execution model distinct from traditional frameworks. Reactive programming emerged to address this issue, as it mitigates the inefficiency of platform threads idling during blocking operations (Figure 2).
Figure 2 – Reactive Event Loop
Quarkus leverages a reactive engine powered by Eclipse Vert.x and Netty, facilitating non-blocking I/O interactions. Mutiny, the preferred approach for writing reactive code with Quarkus, adopts an event-driven paradigm, wherein reactions are triggered by received events.
Mutiny offers two event-driven and lazy types:
While reactive systems offer benefits, we encountered several challenges during development:
"Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write."
―Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship
Here's an example of reactive code using Mutiny to illustrate the complexity:
Multi.createFrom().ticks().every(Duration.ofSeconds(15)) .onItem().invoke(() - > Multi.createFrom().iterable(configs()) .onItem().transform(configuration - > { try { return Tuple2.of(openAPIConfiguration, RestClientBuilder.newBuilder() .baseUrl(new URL(configuration.url())) .build(MyReactiveRestClient.class) .getAPIResponse()); } catch (MalformedURLException e) { log.error("Unable to create url"); } return null; }).collect().asList().toMulti().onItem().transformToMultiAndConcatenate(tuples - > { AtomicInteger callbackCount = new AtomicInteger(); return Multi.createFrom().emitter(emitter - > Multi.createFrom().iterable(tuples) .subscribe().with(tuple - > tuple.getItem2().subscribe().with(response - > { emitter.emit(callbackCount.incrementAndGet()); if (callbackCount.get() == tuples.size()) { emitter.complete(); } }) )); }).subscribe().with(s - > {}, Throwable::printStackTrace, () - > doSomethingUponComplete())) .subscribe().with(aLong - > log.info("Tic Tac with iteration: " + aLong));
Project Loom, a recent development in the Java ecosystem, promises to mitigate the issues associated with blocking operations. By enabling the creation of thousands of virtual threads without hardware changes, Project Loom could potentially eliminate the need for a reactive approach in many cases.
"Project Loom is going to kill Reactive Programming"
―Brian Goetz
In conclusion, our decision to move away from reactive architecture style a pragmatic approach to our project's long-term maintainability. While reactive systems offer potential benefits, the challenges they presented for our team outweighed those advantages in our specific context.
Importantly, this shift did not compromise performance. This is a positive outcome, as it demonstrates that a well-designed non reactive(imperative) architecture can deliver the necessary performance without the complexity associated with reactive architecture in our case.
As we look towards the future, the focus remains on building a codebase that is not only functional but also easy to understand and maintain for developers of all experience levels. This not only reduces development time but also fosters better collaboration and knowledge sharing within the team.
In the graph below, theX-axisrepresents the increasing complexity of our codebase as it evolves, while theY-axisdepicts the time required for these developmental changes.
以上是Why we discarded Reactive systems architecture from our code?的詳細內容。更多資訊請關注PHP中文網其他相關文章!