Utilisation de sous-flux dans l’intégration Spring

Utilisation de sous-flux dans l'intégration Spring

1. Vue d'ensemble

Spring Integration facilite l'utilisation de certainsEnterprise Integration Patterns. L'une de ces méthodes consiste à utiliserits DSL.

Dans ce didacticiel, nous allons examiner la prise en charge par DSL des sous-flux pour simplifier certaines de nos configurations.

2. Notre tâche

Supposons que nous ayons une séquence d'entiers que nous souhaitons séparer en trois compartiments différents.

Et si nous voulions utiliser Spring Integration pour cela, nous pourrions commencer par créer trois canaux de sortie:

  • Les nombres comme 0, 3, 6 et 9 iront auxmultipleOfThreeChannel

  • Les nombres comme 1, 4, 7 et 10 iront auxremainderIsOneChannel

  • Et les nombres comme 2, 5, 8 et 11 vont auxremainderIsTwoChannel

Pour voir à quel point les sous-flux peuvent être utiles, commençons par voir à quoi cela ressemblera sans sous-flux.

Et puis, nous utiliserons des sous-flux pour simplifier notre configuration avec:

  • publierSubscribeChannel

  • routeToRecipients

  • Filters, pour configurer notre logiqueif-then

  • Routers, pour configurer notre logiqueswitch

3. Conditions préalables

Maintenant, avant de configurer nos sous-flux, créons ces canaux de sortie.

Nous allons rendre cesQueueChannels car c'est un peu plus facile à démo:

@EnableIntegration
@IntegrationComponentScan
public class SubflowsConfiguration {

    @Bean
    QueueChannel multipleOfThreeChannel() {
        return new QueueChannel();
    }

    @Bean
    QueueChannel remainderIsOneChannel() {
        return new QueueChannel();
    }

    @Bean
    QueueChannel remainderIsTwoChannel() {
        return new QueueChannel();
    }

    boolean isMultipleOfThree(Integer number) {
       return number % 3 == 0;
    }

    boolean isRemainderIOne(Integer number) {
        return number % 3 == 1;
    }

    boolean isRemainderTwo(Integer number) {
        return number % 3 == 2;
    }
}

En fin de compte, ce sont là où nos numéros groupés finiront.

Notez également que Spring Integration peut facilement commencer à paraître complexe, nous allons donc ajouter quelques méthodes d'aide pour des raisons de lisibilité.

4. Résolution sans sous-flux

Nous devons maintenant définir nos flux.

Sans sous-flux, l’idée simple est de définir trois flux d’intégration distincts, un pour chaque type de numéro.

Nous enverrons la même séquence de messages à chaque composantIntegrationFlow, mais les messages de sortie pour chaque composant seront différents.

4.1. Définition des composantsIntegrationFlow

Tout d'abord, définissons chaque beanIntegrationFlow dans notreSubflowConfiguration class:

@Bean
public IntegrationFlow multipleOfThreeFlow() {
    return flow -> flow.split()
      . filter(this::isMultipleOfThree)
      .channel("multipleOfThreeChannel");
}

Notre flux contient deux points de terminaison - unSplitter  suivi par unFilter.

Le filtre fait ce que cela ressemble. Mais pourquoi avons-nous également besoin d'un séparateur? Nous verrons cela dans une minute, mais en gros, cela divise une entréeCollection en messages individuels.

Et, bien sûr, nous pouvons définir deux haricotsIntegrationFlow supplémentaires de la même manière.

4.2. Passerelles de messagerie

Pour chaque flux, nous avons également besoin d'unMessage Gateway.

Autrement dit, voici l’abrégé de l’API Spring Integration Messages qui s’éloigne de l’appelant, de la même manière qu’un service REST peut extraire HTTP:

@MessagingGateway
public interface NumbersClassifier {

    @Gateway(requestChannel = "multipleOfThreeFlow.input")
    void multipleOfThree(Collection numbers);

    @Gateway(requestChannel = "remainderIsOneFlow.input")
    void remainderIsOne(Collection numbers);

    @Gateway(requestChannel = "remainderIsTwoFlow.input")
    void remainderIsTwo(Collection numbers);

}

Pour chacun, nous devons utiliser l'annotation@Gateway et spécifier le nom implicite du canal d'entrée, qui est simplement le nom du bean suivi de“.input”. Note that we can use this convention because we are using lambda-based flows.

Ces méthodes sont les points d'entrée de nos flux.

4.3. Envoi de messages et vérification de la sortie

Et maintenant, testons:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SeparateFlowsConfiguration.class })
public class SeparateFlowsUnitTest {

    @Autowired
    private QueueChannel multipleOfThreeChannel;

    @Autowired
    private NumbersClassifier numbersClassifier;
    @Test
    public void whenSendMessagesToMultipleOf3Flow_thenOutputMultiplesOf3() {
        numbersClassifier.multipleOfThree(Arrays.asList(1, 2, 3, 4, 5, 6));
        Message outMessage = multipleOfThreeChannel.receive(0);
        assertEquals(outMessage.getPayload(), 3);
        outMessage = multipleOfThreeChannel.receive(0);
        assertEquals(outMessage.getPayload(), 6);
        outMessage = multipleOfThreeChannel.receive(0);
        assertNull(outMessage);
    }
}

Notez que nous avons envoyé les messages en tant queList, c'est pourquoi nous avions besoin du séparateur, pour prendre le "message de liste" unique et le transformer en plusieurs "messages numériques".

Nous appelonsreceive aveco pour obtenir le prochain message disponible sans attendre. Puisqu'il y a deux multiples de trois dans notre liste, nous nous attendons à pouvoir l'appeler deux fois. Le troisième appel àreceive renvoienull.

receive, of bien sûr, renvoie unMessage, donc nous appelonsgetPayload pour extraire le nombre.

De même, nous pourrions faire la même chose pour les deux autres.

So, that was the solution without subflows. Nous avons trois flux distincts à maintenir et trois méthodes de passerelle distinctes.

Ce que nous allons faire maintenant est de remplacer les troisIntegrationFlow beans par un seul bean et les trois méthodes de passerelle par une seule.

5. Utilisation depublishSubscribeChannel

La méthodepublishSubscribeChannel() diffuse des messages vers tous les sous-flux abonnés. De cette façon, nous pouvons créer un flux au lieu de trois.

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        .publishSubscribeChannel(subscription ->
           subscription
             .subscribe(subflow -> subflow
               . filter(this::isMultipleOfThree)
               .channel("multipleOfThreeChannel"))
             .subscribe(subflow -> subflow
                . filter(this::isRemainderOne)
                .channel("remainderIsOneChannel"))
             .subscribe(subflow -> subflow
                . filter(this::isRemainderTwo)
                .channel("remainderIsTwoChannel")));
}

De cette façon,the subflows are anonymous, meaning that they can’t be independently addressed.

Maintenant, nous n'avons qu'un seul flux, alors éditons également notreNumbersClassifier :

@Gateway(requestChannel = "classify.input")
void classify(Collection numbers);

Maintenant,since we have only one IntegrationFlow bean and one gateway method, we need only send our list once:

@Test
public void whenSendMessagesToFlow_thenNumbersAreClassified() {
    numbersClassifier.classify(Arrays.asList(1, 2, 3, 4, 5, 6));

    // same assertions as before
}

Notez qu'à partir de maintenant, seule la définition du flux d'intégration changera afin que nous ne montrions plus le test.

6. Utilisation derouteToRecipients

Une autre façon d'obtenir la même chose estrouteToRecipients, ce qui est bien car il a un filtrage intégré.

En utilisant cette méthode,we can specify both channels and subflows for broadcasting. 

6.1. recipient

Dans le code ci-dessous, nous spécifieronsmultipleof3Channel,remainderIs1Channel, etremainderIsTwoChannel comme destinataires en fonction de nos conditions:

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        .routeToRecipients(route -> route
          . recipient("multipleOfThreeChannel",
            this::isMultipleOfThree)
          . recipient("remainderIsOneChannel",
            this::isRemainderOne)
          . recipient("remainderIsTwoChannel",
            this::isRemainderTwo));
}

Nous pouvons également appelerrecipient ans condition, etrouteToRecipients publie sur cette destination sans condition.

6.2. recipientFlow

Et notez querouteToRecipients nous permet de définir un flux complet, tout commepublishSubscribeChannel. 

Modifions le code ci-dessus et spécifions unanonymous subflow as the first recipient:

.routeToRecipients(route -> route
  .recipientFlow(subflow -> subflow
      . filter(this::isMultipleOfThree)
      .channel("mutipleOfThreeChannel"))
  ...);

This subflow will receive the entire sequence of messages, donc nous devons filtrer comme avant pour obtenir le même comportement.

Encore une fois, un haricotIntegrationFlow nous suffisait.

Passons maintenant aux composantsif-else. L'un d'eux estFilter.

7. Utilisation des fluxif-then

Nous avons déjà utiliséFilter dans tous les exemples précédents. La bonne nouvelle est que nous pouvons spécifier non seulement la condition pour un traitement ultérieur, mais aussia channel or aflow for the discarded messages.

Nous pouvons penser à rejeter les flux et les canaux comme un sblocelse :

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        . filter(this::isMultipleOfThree,
           notMultiple -> notMultiple
             .discardFlow(oneflow -> oneflow
               . filter(this::isRemainderOne,
                 twoflow -> twoflow
                   .discardChannel("remainderIsTwoChannel"))
               .channel("remainderIsOneChannel"))
        .channel("multipleofThreeChannel");
}

Dans ce cas, nous avons implémenté notre logique de routageif-else:

  • If le nombre n'est pas un multiple de trois,then rejette ces messages dans le flux de rejet; we use a flow here since there is more logic needed to know its destination channel.

  • Dans le flux de rejet,if le nombre n'est pas du reste un,then rejette ces messages vers le canal de rejet.

8. switch-chante sur une valeur calculée

Et enfin, essayons la méthoderoute , qui nous donne un peu plus de contrôle querouteToRecipients. C'est bien car unRouter peut diviser le flux en un nombre quelconque de parties, alors qu'un scanFilter faites seulement deux.

8.1. channelMapping

Définissons notre beanIntegrationFlow:

@Bean
public IntegrationFlow classify() {
    return classify -> classify.split()
      . route(number -> number % 3,
        mapping -> mapping
         .channelMapping(0, "multipleOfThreeChannel")
         .channelMapping(1, "remainderIsOneChannel")
         .channelMapping(2, "remainderIsTwoChannel"));
}

Dans le code ci-dessus, nous calculons une clé de routage en effectuant la division:

route(p -> p % 3,...

Sur la base de cette clé, nous routons les messages:

channelMapping(0, "multipleof3Channel")

8.2. subFlowMapping

Maintenant, comme avec d'autres, nous pouvons prendre plus de contrôle en spécifiant un sous-flux, en remplaçantchannelMapping parsubFlowMapping:

.subFlowMapping(1, subflow -> subflow.channel("remainderIsOneChannel"))

Ou encore plus de contrôle en appelant la méthodehandle au lieu de la méthodechannel :

.subFlowMapping(2, subflow -> subflow
  . handle((payload, headers) -> {
      // do extra work on the payload
     return payload;
  }))).channel("remainderIsTwoChannel");

Dans ce cas, le sous-flux reviendrait au flux principal après la méthoderoute(), il faudrait donc spécifier le canalremainderIsTwoChannel.

9. Conclusion

Dans ce didacticiel, nous avons exploré comment filtrer et acheminer les messages de différentes manières à l'aide de sous-flux.

Comme d'habitude, le code source complet est disponibleon GitHub.