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));
Le projet Loom, un développement récent de l'écosystème Java, promet d'atténuer les problèmes associés au blocage des opérations. En permettant la création de milliers de threads virtuels sans modifications matérielles, Project Loom pourrait potentiellement éliminer le besoin d'une approche réactive dans de nombreux cas.
"Le projet Loom va tuer la programmation réactive"
―Brian Goetz
En conclusion, notre décision de s'éloigner du style d'architecture réactive pour adopter une approche pragmatique de la maintenabilité à long terme de notre projet. Bien que les systèmes réactifs offrent des avantages potentiels, les défis qu'ils ont présentés à notre équipe l'emportaient sur ces avantages dans notre contexte spécifique.
Il est important de noter que ce changement n'a pas compromis les performances. Il s'agit d'un résultat positif, car il démontre qu'une architecture non réactive (impérative) bien conçue peut fournir les performances nécessaires sans la complexité associée à l'architecture réactive dans notre cas.
Alors que nous regardons vers l'avenir, l'accent reste mis sur la création d'une base de code non seulement fonctionnelle, mais également facile à comprendre et à maintenir pour les développeurs de tous niveaux d'expérience. Cela réduit non seulement le temps de développement, mais favorise également une meilleure collaboration et un meilleur partage des connaissances au sein de l'équipe.
Dans le graphique ci-dessous, l'axe Xreprésente la complexité croissante de notre base de code à mesure qu'elle évolue, tandis que l'axe Yreprésente le temps requis pour ces changements de développement.
The above is the detailed content of Why we discarded Reactive systems architecture from our code?. For more information, please follow other related articles on the PHP Chinese website!