Introduction au réacteur à ressort

Introduction au réacteur à ressort

1. Vue d'ensemble

Dans cet article rapide, nous présenterons le projet Spring Reactor. Nous allons mettre en place un scénario réel pour une application réactive et événementielle.

2. Les bases de Spring Reactor

2.1. Pourquoi Reactor?

La conception réactivepattern est une architecture basée sur les événements pour la gestion asynchrone d'un grand volume de demandes de service simultanées provenant d'un ou de plusieurs gestionnaires de services.

Et le projet Spring Reactor est basé sur ce modèle et a pour objectif clair et ambitieux de créer des applications asynchrones et réactives sur lesJVM.

2.2. Exemples de scénarios

Avant de commencer, voici quelques scénarios intéressants dans lesquels il serait judicieux de tirer parti du style architectural réactif pour obtenir une idée de l'endroit où vous pourriez l'appliquer:

  • Service de notification de grandes applications d'achat en ligne comme Amazon

  • Énormes services de traitement de transactions du secteur bancaire

  • Part trade business où les prix des actions changent simultanément

Une note rapide à prendre en compte est que l'implémentation du bus d'événements n'offre aucune persistance des événements; tout comme le bus Spring Event par défaut, c'est une implémentation en mémoire.

3. Dépendances Maven

Commençons à utiliser Spring Reactor en ajoutant la dépendance suivante dans nospom.xml:


    io.projectreactor
    reactor-bus
    2.0.8.RELEASE

Vous pouvez vérifier la dernière version de Reactor-Bus enCentral Maven Repository.

4. Créer une application de démonstration

Pour mieux comprendre les avantages de l'approche par réacteur,let’s look at a practical example.

Nous allons créer une application de notification simple, qui avertirait les utilisateurs par e-mail et SMS, une fois leur commande terminée sur une boutique en ligne.

Une implémentation synchrone typique serait naturellement liée au débit du service SMS. Les pics de trafic, ces vacances seraient généralement problématiques.

Avec une approche réactive, le système peut être plus flexible et mieux s'adapter aux pannes ou aux délais d'attente dans ces types de systèmes externes, tels que les serveurs de messagerie ou de messagerie SMS.

Jetons un coup d'œil à l'application - en commençant par les aspects plus traditionnels et en passant aux constructions les plus réactives.

4.1. POJO simple

Commençons par créer une classePOJO pour représenter les données de notification:

public class NotificationData {

    private long id;
    private String name;
    private String email;
    private String mobile;

    // getter and setter methods
}

4.2. La couche de service

Configurons maintenant une couche de service simple:

public interface NotificationService {

    void initiateNotification(NotificationData notificationData)
      throws InterruptedException;

}

Et la mise en œuvre, simulant une longue opération ici:

@Service
public class NotificationServiceimpl implements NotificationService {

    @Override
    public void initiateNotification(NotificationData notificationData)
      throws InterruptedException {

      System.out.println("Notification service started for "
        + "Notification ID: " + notificationData.getId());

      Thread.sleep(5000);

      System.out.println("Notification service ended for "
        + "Notification ID: " + notificationData.getId());
    }
}

Notez que pour illustrer un scénario réel d'envoi de messages via une passerelle SMS ou une passerelle de messagerie, nous introduisons intentionnellement un délai de 5 secondes dans la méthodeinitiateNotification deThread.sleep(5000).

Et ainsi, lorsque le fil de discussion frappe le service - il sera bloqué pendant 5 secondes.

4.3. Le consommateur

Passons maintenant aux aspects les plus réactifs de notre application et implémentons un consommateur - que nous mapperons ensuite auxreactor event bus:

@Service
public class NotificationConsumer implements
  Consumer> {

    @Autowired
    private NotificationService notificationService;

    @Override
    public void accept(Event notificationDataEvent) {
        NotificationData notificationData = notificationDataEvent.getData();

        try {
            notificationService.initiateNotification(notificationData);
        } catch (InterruptedException e) {
            // ignore
        }
    }
}

Comme vous pouvez le voir, le consommateur implémente simplement l'interfaceConsumer<T> - avec une seule méthodeaccept. C'est cette implémentation simple qui exécute la logique principale, tout commea typical Spring listener.

4.4. Le controlle

Enfin, maintenant que nous sommes en mesure de consommer les événements, générons-les également.

Nous allons faire cela dans un simple contrôleur:

@Controller
public class NotificationController {

    @Autowired
    private EventBus eventBus;

    @GetMapping("/startNotification/{param}")
    public void startNotification(@PathVariable Integer param) {
        for (int i = 0; i < param; i++) {
            NotificationData data = new NotificationData();
            data.setId(i);

            eventBus.notify("notificationConsumer", Event.wrap(data));

            System.out.println(
              "Notification " + i + ": notification task submitted successfully");
        }
    }
}

C'est assez explicite - nous envoyons des événements via lesEventBus ici - en utilisant une clé unique.

En bref, lorsqu'un client frappe l'URL avec la valeur param 10, un total de 10 événements sera envoyé via le bus.

4.5. La configuration Java

Nous avons presque terminé; rassemblons tout avec Java Config et créons notre application Boot:

import static reactor.bus.selector.Selectors.$;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application implements CommandLineRunner {

    @Autowired
    private EventBus eventBus;

    @Autowired
    private NotificationConsumer notificationConsumer;

    @Bean
    Environment env() {
        return Environment.initializeIfEmpty().assignErrorJournal();
    }

    @Bean
    EventBus createEventBus(Environment env) {
        return EventBus.create(env, Environment.THREAD_POOL);
    }

    @Override
    public void run(String... args) throws Exception {
        eventBus.on($("notificationConsumer"), notificationConsumer);
    }

    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }
}

C'est ici que nous créons le beanEventBus via l'API statiquecreate dansEventBus.

Dans notre cas, nous instancions le bus d'événements avec un pool de threads par défaut disponible dans l'environnement.

Si nous voulions un peu plus de contrôle sur le bus, nous pourrions également fournir un nombre de threads à l'implémentation:

EventBus evBus = EventBus.create(
  env,
  Environment.newDispatcher(
    REACTOR_THREAD_COUNT,REACTOR_THREAD_COUNT,
    DispatcherType.THREAD_POOL_EXECUTOR));

Ensuite, notez également comment nous utilisons l'importation statique de l'attribut$ ici.

La fonctionnalité fournit un mécanisme de type sécurisé pour inclure des constantes (dans notre cas, c'est $ attribute) dans le code sans avoir à référencer la classe qui a défini le champ à l'origine.

Nous utilisons cette fonctionnalité dans notre implémentation de la méthoderun -where we’re registering our consumer to be triggered when the matching notification.

Ceci est basé sura unique selector key qui permet d'identifier chaque consommateur.

5. Testez l'application

Après avoir exécuté une compilation Maven, nous pouvons maintenant simplement exécuterjava -jar name_of_the_application.jar pour exécuter l'application.

Créons maintenant une petite classe de test JUnit pour tester l'application. Nous utiliserions lesSpringJUnit4ClassRunner de Spring Boot pour créer le scénario de test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Application.class})
public class DataLoader {

    @Test
    public void exampleTest() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getForObject(
          "http://localhost:8080/startNotification/10", String.class);
    }
}

Maintenant, exécutons ce scénario de test pour tester l'application:

Notification 0: notification task submitted successfully
Notification 1: notification task submitted successfully
Notification 2: notification task submitted successfully
Notification 3: notification task submitted successfully
Notification 4: notification task submitted successfully
Notification 5: notification task submitted successfully
Notification 6: notification task submitted successfully
Notification 7: notification task submitted successfully
Notification 8: notification task submitted successfully
Notification 9: notification task submitted successfully
Notification service started for Notification ID: 1
Notification service started for Notification ID: 2
Notification service started for Notification ID: 3
Notification service started for Notification ID: 0
Notification service ended for Notification ID: 1
Notification service ended for Notification ID: 0
Notification service started for Notification ID: 4
Notification service ended for Notification ID: 3
Notification service ended for Notification ID: 2
Notification service started for Notification ID: 6
Notification service started for Notification ID: 5
Notification service started for Notification ID: 7
Notification service ended for Notification ID: 4
Notification service started for Notification ID: 8
Notification service ended for Notification ID: 6
Notification service ended for Notification ID: 5
Notification service started for Notification ID: 9
Notification service ended for Notification ID: 7
Notification service ended for Notification ID: 8
Notification service ended for Notification ID: 9

Comme vous pouvez le constater, dès que le noeud final est touché, les 10 tâches sont soumises instantanément sans créer de blocage. Et une fois soumis, les événements de notification sont traités en parallèle.

Gardez à l'esprit que dans notre scénario, il n'est pas nécessaire de traiter ces événements dans n'importe quel ordre.

6. Conclusion

Dans cette petite application, nous obtenons certainement une augmentation du débit, ainsi qu'une application globalement plus sage.

Cependant, ce scénario ne fait que gratter la surface et constitue simplement une bonne base pour commencer à comprendre le paradigme réactif.

Comme toujours, le code source est disponibleover on GitHub.