Traitement asynchrone avec Spring Boot : utilisation de @Async et CompletableFuture

Traitement asynchrone avec Spring Boot : utilisation de @Async et CompletableFuture

Dans les applications modernes, la haute scalabilité et la réactivité sont essentielles. Une manière d'atteindre ces objectifs dans une application Spring Boot est d'utiliser le traitement asynchrone. L'annotation @Async et la classe CompletableFuture sont des outils puissants permettant d'exécuter des tâches dans des threads séparés sans bloquer le thread principal. Cette approche peut améliorer considérablement les performances des applications nécessitant des processus non bloquants et concurrents, tels que les services de notification, l'envoi de SMS ou l'exécution de tâches en arrière-plan.

Nous allons explorer les avantages de l'utilisation de la programmation asynchrone avec @Async et CompletableFuture dans Spring Boot. Par la suite j'aimerais bien apporter une comparaison également entre cette approche et celle avec des systèmes de message-broker comme RabbitMQ et Kafka.

Avantages de l'utilisation de @Async et CompletableFuture dans Spring Boot

1. Opérations non-bloquantes

Le principal avantage de l'utilisation de @Async avec CompletableFuture est qu'il permet d'exécuter des opérations non-bloquantes. Lorsque vous marquez une méthode avec @Async, elle s'exécute dans un thread séparé, permettant au thread principal de l'application de continuer son travail sans attendre la fin de la tâche asynchrone.

  • Amélioration de la réactivité : les utilisateurs peuvent continuer à interagir avec l'application pendant que les tâches en arrière-plan telles que l'envoi d'e-mails ou de notifications par SMS sont traitées de manière asynchrone.
  • Meilleure scalabilité : comme plusieurs opérations peuvent être traitées en parallèle cette approche permet à l'application de monter en charge facilement en fonction de l'augmentation de la demande.

2. Architecture simplifiée

Comparé aux systèmes de messagerie comme RabbitMQ ou Kafka, @Async et CompletableFuture offrent une architecture plus simple pour gérer les tâches asynchrones. Avec les brokers de messages il est nécessaire de configurer des producteurs, des consommateurs et des files d'attente, ajoutant une complexité supplémentaire à l'application.

  • Pas d'Infrastructure Additionnelle : vous n'avez pas besoin de gérer une infrastructure externe telle que des files d'attente ou des brokers.
  • Temps de Développement Réduit : Spring Boot fournit un support intégré pour @Async, ce qui le rend plus facile à mettre en œuvre sans nécessiter de dépendances externes.

3. Exécution Concurente

L'utilisation de CompletableFuture.runAsync() permet aux tâches de s'exécuter de manière concurrente, répartissant la charge sur plusieurs threads. Cette concurrence est bénéfique dans les scénarios où les tâches sont indépendantes, comme l'envoi de plusieurs notifications ou le traitement d'opérations indépendantes.

Comme plusieurs tâches peuvent s'exécuter en parallèle sur différents threads, les ressources CPU et mémoire sont utilisées plus efficacement de ce fait on note une optimisations des ressources.

4. Gestion des exceptions

Un autre avantage important de l'utilisation de CompletableFuture est la gestion améliorée des exceptions via des méthodes enchaînées telles que handle(), exceptionally(), ou whenComplete().

@Async et CompletableFuture vs RabbitMQ et Kafka

Aperçu de RabbitMQ et Kafka

RabbitMQ et Kafka sont deux systèmes de message-broker largement utilisés pour permettre la communication asynchrone entre microservices. Ces systèmes sont idéaux pour des cas d'utilisation plus complexes où un débit élevé et un découplage des services sont requis. Cependant, ils nécessitent également une configuration et une gestion supplémentaires de l'infrastructure.

  • RabbitMQ : principalement utilisé pour la messagerie en temps réel et la mise en file d'attente des tâches, RabbitMQ gère les messages via son mécanisme de file d'attente, où les producteurs envoient des messages et les consommateurs les traitent de manière asynchrone.
  • Kafka : connu pour son débit élevé et sa nature distribuée, Kafka est généralement utilisé pour des architectures événementielles, où de grands volumes de flux d'événements doivent être traités de manière asynchrone.

Quand Utiliser @Async et CompletableFuture

  • Tâches simples et légères : si votre cas d'utilisation implique des tâches simples comme l'envoi de notifications, l'exécution de tâches en arrière-plan ou le traitement de requêtes web asynchrones, l'utilisation de @Async et CompletableFuture est un bon choix. Cela nécessite moins de surcharge et est plus facile à mettre en œuvre que RabbitMQ ou Kafka.
  • Pas de communication inter-services : si la tâche n'a pas besoin de communiquer avec d'autres services ou de persister des messages pour une utilisation future, il est plus efficace d'utiliser les capacités asynchrones intégrées de Spring au lieu de déployer un broker de messages.

Quand utiliser RabbitMQ ou Kafka

RabbitMQ et Kafka sont des solutions particulièrement adaptées aux systèmes complexes et distribués nécessitant la gestion de volumes élevés de messages entre différents services.

RabbitMQ excelle dans les scénarios de messagerie classique où des échanges fiables, la priorisation des messages et le routage complexe sont requis. En revanche, Kafka se distingue par sa capacité à stocker les messages de manière persistante sur une longue période permettant non seulement la transmission en temps réel mais aussi la relecture de messages pour des besoins comme l’event sourcing. Cela fait de Kafka un choix privilégié lorsqu'il s'agit de traiter des flux de données volumineux et continus, et de garantir que les événements peuvent être rejoués ou reprocessés ultérieurement, offrant ainsi une forte tolérance aux erreurs et une excellente scalabilité dans les architectures distribuées.

Cas pratique : service de notification sms asynchrone

Voyons maintenant un exemple pratique où nous créons un service de notification SMS dans Spring Boot. Ce service enverra des messages SMS de manière asynchrone en utilisant @Async et CompletableFuture.

Étape 1 : Activer le support asynchrone dans Spring Boot

Tout d'abord, vous devez activer le traitement asynchrone dans votre application Spring Boot. Vous pouvez le faire en ajoutant l'annotation @EnableAsync à votre classe principale d'application.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class AsyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }
}

Étape 2 : Créer le service de notification SMS

Ensuite, nous allons créer un service qui envoie des notifications par SMS. La méthode qui envoie le SMS sera annotée avec @Async pour s'assurer qu'elle s'exécute de manière asynchrone.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

@Service
public class SmsNotificationService {

    private static final Logger logger = LoggerFactory.getLogger(SmsNotificationService.class);

    @Async
    public CompletableFuture<Void> sendSmsAsync(String phoneNumber, String message) {
        logger.info("Envoi du SMS à {}", phoneNumber);
        try {
            // Simuler un processus d'envoi de SMS long
            Thread.sleep(2000);
            logger.info("SMS envoyé avec succès à {}", phoneNumber);
        } catch (InterruptedException e) {
            logger.error("Erreur lors de l'envoi du SMS", e);
        }
        return CompletableFuture.completedFuture(null);
    }
}

Étape 3 : Créer un contrôleur pour déclencher l'envoi du SMS

Nous créons maintenant un contrôleur simple qui déclenche le service de notification SMS de manière asynchrone.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.CompletableFuture;

@RestController
public class SmsController {

    @Autowired
    private SmsNotificationService smsNotificationService;

    @GetMapping("/send-sms")
    public String sendSms(@RequestParam String phoneNumber, @RequestParam String message) {
        CompletableFuture<Void> smsTask = smsNotificationService.sendSmsAsync(phoneNumber, message);
        return "Le SMS est en cours d'envoi de manière asynchrone !";
    }
}

Étape 4 : Exécution de l'application

Lorsque vous exécutez cette application Spring Boot et accédez à l'endpoint /send-sms, la notification SMS sera envoyée en arrière-plan, permettant au thread principal de répondre immédiatement à l'utilisateur sans attendre la fin de l'envoi du SMS.

Par exemple, en faisant la requête HTTP suivante :

GET /send-sms?phoneNumber=1234567890&message=Bonjour

Cette requête déclenchera le service de notification SMS asynchrone, et vous verrez immédiatement la réponse "Le SMS est en cours d'envoi de manière asynchrone !", tandis que l'envoi réel se produira dans un thread séparé.

L'utilisation de @Async et CompletableFuture dans Spring Boot fournit une manière simple et efficace d'implémenter le traitement asynchrone pour des tâches légères telles que les notifications par SMS. Bien que des systèmes de message-broker comme RabbitMQ et Kafka soient excellents pour des cas d'utilisation plus complexes, tirer parti des capacités asynchrones de Spring est souvent préférable pour des tâches simples et non bloquantes qui ne nécessitent pas la persistance des messages ou la communication inter-services.

En permettant des opérations non bloquantes, en simplifiant l'architecture et en améliorant la réactivité, le traitement asynchrone permet aux applications Spring Boot de gérer des tâches concurrentes de manière plus efficace, sans ajouter de complexité significative.

TakkJokk,