Modèles d’intégration avec Apache Camel

Modèles d'intégration avec Apache Camel

1. Vue d'ensemble

Cet article couvre certains modèles d’intégration d’entreprise essentiels (EIP) pris en charge par Apache Camel. Les modèles d’intégration aident en fournissant des solutions pour des méthodes standardisées d’intégration de systèmes.

Si vous devez d'abord passer en revue les bases d'Apache Camel, visitez définitivementthis article pour vous familiariser avec les bases.

2. À propos des EIP

Les modèles d'intégration d'entreprise sont des modèles de conception qui visent à fournir des solutions aux problèmes d'intégration. Camel fournit des implémentations pour beaucoup de ces modèles. Pour voir la liste complète des modèles pris en charge, visitezthis link.

Dans cet article, nous aborderons les modèles d'intégration de routeur basé sur le contenu, de traducteur de messages, de multidiffusion, de répartiteur et de canal de lettre morte.

2. Routeur basé sur le contenu

Content Based Router est un routeur de messages qui achemine un message vers sa destination en fonction d'un en-tête de message, d'une partie de la charge utile ou de tout élément d'échange de message considéré comme un contenu.

Il commence par l'instruction DSLchoice() suivie d'une ou plusieurs instructions DSLwhen(). Chaquewhen() contient une expression de prédicat qui, si elle est satisfaite, entraînera l'exécution des étapes de traitement contenues.

Illustrons cet EIP en définissant une route qui consomme les fichiers d’un dossier et les déplace dans deux dossiers différents en fonction de l’extension de fichier. Notre route est référencée dans un fichier XML Spring en utilisant une syntaxe XML personnalisée pour Camel:




    

La définition de la route est contenue dans la classeContentBasedFileRouter où les fichiers sont acheminés du dossier source vers deux dossiers de destination différents en fonction de leur extension.

Alternativement, nous pourrions utiliser l'approche de configuration Spring Java ici, par opposition à l'utilisation d'un fichier XML Spring. Pour ce faire, nous devons ajouter une dépendance supplémentaire à notre projet:


    org.apache.camel
    camel-spring-javaconfig
    2.18.1

La dernière version de l'artefact peut être trouvéehere.

Après cela, nous devons étendre la classeCamelConfiguration et remplacer la méthoderoutes() qui référenceraContentBasedFileRouter:

@Configuration
public class ContentBasedFileRouterConfig extends CamelConfiguration {

    @Bean
    ContentBasedFileRouter getContentBasedFileRouter() {
        return new ContentBasedFileRouter();
    }

    @Override
    public List routes() {
        return Arrays.asList(getContentBasedFileRouter());
    }
}

L'extension est évaluée à l'aide deSimple Expression Language via l'instruction DSLsimple () qui était destinée à être utilisée pour évaluer les expressions et les prédicats:

public class ContentBasedFileRouter extends RouteBuilder {

    private static final String SOURCE_FOLDER
      = "src/test/source-folder";
    private static final String DESTINATION_FOLDER_TXT
      = "src/test/destination-folder-txt";
    private static final String DESTINATION_FOLDER_OTHER
      = "src/test/destination-folder-other";

    @Override
    public void configure() throws Exception {
        from("file://" + SOURCE_FOLDER + "?delete=true").choice()
          .when(simple("${file:ext} == 'txt'"))
          .to("file://" + DESTINATION_FOLDER_TXT).otherwise()
          .to("file://" + DESTINATION_FOLDER_OTHER);
    }
}

Ici, nous utilisons en plus l'instruction DSL deotherwise() afin de router tous les messages qui ne satisfont pas les prédicats donnés avec les instructionswhen().

3. Traducteur de messages

Étant donné que chaque système utilise son propre format de données, il est souvent nécessaire de traduire le message provenant d’un autre système dans le format de données pris en charge par le système de destination.

Camel prend en charge le routeurMessageTranslator qui nous permet de transformer des messages en utilisant soit un processeur personnalisé dans la logique de routage, en utilisant un bean spécifique pour effectuer la transformation ou en utilisant l'instruction DSL detransform().

Un exemple d'utilisation d'un processeur personnalisé peut être trouvé dans lesprevious article où nous avons défini un processeur qui ajoute un horodatage au nom de fichier de chaque fichier entrant.

Voyons maintenant comment utiliser Message Translator à l'aide de l'instructiontransform():

public class MessageTranslatorFileRouter extends RouteBuilder {
    private static final String SOURCE_FOLDER
      = "src/test/source-folder";
    private static final String DESTINATION_FOLDER
      = "src/test/destination-folder";

    @Override
    public void configure() throws Exception {
        from("file://" + SOURCE_FOLDER + "?delete=true")
          .transform(body().append(header(Exchange.FILE_NAME)))
          .to("file://" + DESTINATION_FOLDER);
    }
}

Dans cet exemple, nous ajoutons le nom de fichier au contenu du fichier via l'instructiontransform() pour chaque fichier du dossier source et nous déplaçons les fichiers transformés vers un dossier de destination.

4. Multidiffusion

La multidiffusion nous permet deroute the same message to a set of different endpoints and process them in a different way.

Cela est possible en utilisant l'instruction DSL demulticast(), puis en répertoriant les points de terminaison et les étapes de traitement qu'ils contiennent.

Par défaut, le traitement sur différents points de terminaison n'est pas effectué en parallèle, mais cela peut être modifié à l'aide de l'instruction DSLparallelProcessing().

Camel utilisera la dernière réponse comme message sortant après les multidiffusions par défaut. Cependant, il est possible de définir une stratégie d'agrégation différente à utiliser pour assembler les réponses des multidiffusions.

Voyons à quoi ressemble l'EIP de multidiffusion sur un exemple. Nous multidiffuserons les fichiers du dossier source sur deux itinéraires différents où nous transformerons leur contenu et les enverrons vers différents dossiers de destination. Ici, nous utilisonsdirect: component qui nous permet de relier deux routes ensemble:

public class MulticastFileRouter extends RouteBuilder {
    private static final String SOURCE_FOLDER
      = "src/test/source-folder";
    private static final String DESTINATION_FOLDER_WORLD
      = "src/test/destination-folder-world";
    private static final String DESTINATION_FOLDER_HELLO
      = "src/test/destination-folder-hello";

    @Override
    public void configure() throws Exception {
        from("file://" + SOURCE_FOLDER + "?delete=true")
          .multicast()
          .to("direct:append", "direct:prepend").end();

        from("direct:append")
          .transform(body().append("World"))
          .to("file://" + DESTINATION_FOLDER_WORLD);

        from("direct:prepend")
           .transform(body().prepend("Hello"))
           .to("file://" + DESTINATION_FOLDER_HELLO);
    }
}

5. Splitter

Le séparateur nous permet desplit the incoming message into a number of pieces and processing each of them individually. Ceci est possible en utilisant l'instruction DSL desplit().

Contrairement à la multidiffusion, Splitter modifiera le message entrant, tandis que Multicast le laissera tel quel.

Pour illustrer cela sur un exemple, nous allons définir un itinéraire où chaque ligne d'un fichier est divisée et transformée en un fichier individuel qui est ensuite déplacé vers un dossier de destination différent. Chaque nouveau fichier sera créé avec un nom de fichier égal au contenu du fichier:

public class SplitterFileRouter extends RouteBuilder {
    private static final String SOURCE_FOLDER
      = "src/test/source-folder";
    private static final String DESTINATION_FOLDER
      = "src/test/destination-folder";

    @Override
    public void configure() throws Exception {
        from("file://" + SOURCE_FOLDER + "?delete=true")
          .split(body().convertToString().tokenize("\n"))
          .setHeader(Exchange.FILE_NAME, body())
          .to("file://" + DESTINATION_FOLDER);
    }
}

6. Chaîne de lettre morte

Il est fréquent et on peut s’attendre à ce que des problèmes surviennent parfois, par exemple des blocages de base de données, qui peuvent empêcher le message d’être remis comme prévu. Cependant, dans certains cas, réessayer avec un certain délai aidera et un message sera traité.

Dead Letter Channel allows us to control what happens with a message once it fails to be delivered. En utilisant Dead Letter Channel, nous pouvons spécifier s'il faut propager l'exception levée à l'appelant et où acheminer l'échange échoué.

Lorsqu'un message ne parvient pas à être remis, Dead Letter Channel (le cas échéant) déplace le message vers le point d'extrémité de la lettre morte.

Démontrons ceci sur un exemple en lançant une exception sur l'itinéraire:

public class DeadLetterChannelFileRouter extends RouteBuilder {
    private static final String SOURCE_FOLDER
      = "src/test/source-folder";

    @Override
    public void configure() throws Exception {
        errorHandler(deadLetterChannel("log:dead?level=ERROR")
          .maximumRedeliveries(3).redeliveryDelay(1000)
          .retryAttemptedLogLevel(LoggingLevel.ERROR));

        from("file://" + SOURCE_FOLDER + "?delete=true")
          .process(exchange -> {
            throw new IllegalArgumentException("Exception thrown!");
        });
    }
}

Ici, nous avons défini unerrorHandler qui enregistre les échecs de livraison et définit la stratégie de redistribution. En définissantretryAttemptedLogLevel(), chaque tentative de nouvelle livraison sera enregistrée avec le niveau de journal spécifié.

Pour que cela soit pleinement fonctionnel, nous devons également configurer un enregistreur.

Après l'exécution de ce test, les instructions de journal suivantes sont visibles dans une console:

ERROR DeadLetterChannel:156 - Failed delivery for
(MessageId: ID-ZAG0025-50922-1481340325657-0-1 on
ExchangeId: ID-ZAG0025-50922-1481340325657-0-2).
On delivery attempt: 0 caught: java.lang.IllegalArgumentException:
Exception thrown!
ERROR DeadLetterChannel:156 - Failed delivery for
(MessageId: ID-ZAG0025-50922-1481340325657-0-1 on
ExchangeId: ID-ZAG0025-50922-1481340325657-0-2).
On delivery attempt: 1 caught: java.lang.IllegalArgumentException:
Exception thrown!
ERROR DeadLetterChannel:156 - Failed delivery for
(MessageId: ID-ZAG0025-50922-1481340325657-0-1 on
ExchangeId: ID-ZAG0025-50922-1481340325657-0-2).
On delivery attempt: 2 caught: java.lang.IllegalArgumentException:
Exception thrown!
ERROR DeadLetterChannel:156 - Failed delivery for
(MessageId: ID-ZAG0025-50922-1481340325657-0-1 on
ExchangeId: ID-ZAG0025-50922-1481340325657-0-2).
On delivery attempt: 3 caught: java.lang.IllegalArgumentException:
Exception thrown!
ERROR dead:156 - Exchange[ExchangePattern: InOnly,
BodyType: org.apache.camel.component.file.GenericFile,
Body: [Body is file based: GenericFile[File.txt]]]

Comme vous pouvez le constater, chaque tentative de livraison est consignée et affiche Exchange pour lequel la livraison a échoué.

7. Conclusion

Dans cet article, nous avons présenté une introduction aux modèles d'intégration à l'aide d'Apache Camel et en avons présenté quelques exemples.

Nous avons montré comment utiliser ces modèles d'intégration et pourquoi ils sont utiles pour résoudre les problèmes d'intégration.

Le code de cet article peut être trouvéover on GitHub.