Tutoriel pour développer un batch Java avec Easy Batch en moins de 5 minutes

Le but de cet article est d'introduire le framework Easy BatchEasy Batch et de montrer à travers une étude de cas comment il peut faciliter le développement de batchs en Java. 5 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Le but de cet article est de présenter le framework Easy Batch et de montrer à travers une étude de cas comment il peut vous aider à implémenter rapidement vos batchs en Java.

Easy Batch est un framework de traitement de données qui permet de faciliter le développement d'application de type batch en Java. L'idée du framework est de prendre en charge les tâches fastidieuses (lecture des données, filtrage, parsing, mapping à des objets du domaine et validation) et de laisser le développeur se concentrer sur la logique métier de son application.

Dans le vocabulaire Easy Batch, un record représente un item de la source de données. Un record peut être une ligne dans un fichier plat, une balise dans un fichier xml, un tuple d'une table de base de données, etc. Il est composé d'un header contenant des métadonnées sur le record (numéro, date de création, etc.) et d'un payload représentant la donnée lue de la source de données. La figure 1 représente quelques exemples de record :

Image non disponible
Figure 1 : Exemple de records dans Easy Batch

Un job Easy Batch traite une source de données comme illustré dans la figure 2 :

Image non disponible
Figure 2 : Architecture d'un job Easy Batch
  1. La lecture des données est effectuée par un RecordReader de façon séquentielle record par record ;
  2. Le traitement de chaque record est fait par un ou plusieurs RecordProcessor qui sont exécutés en pipeline ;
  3. À la fin du job, Easy Batch fournit un rapport d'exécution et un fichier de log permettant de voir les lignes traitées avec succès, les lignes traitées avec erreurs, le temps d'exécution et plusieurs autres métriques.

Il est aussi possible de traiter les données en mode batch où plusieurs records sont lus et traités par lots. La figure 3 montre un exemple de traitement en mode batch :

Image non disponible
Figure 3 : Traitement de données en mode batch

Pour l'instant, c'est tout ce dont vous avez besoin pour comprendre les concepts de base du framework. Voyons maintenant un étude de cas concrète.

II. Cas d'utilisation

Considérons qu'on veut traiter un fichier CSV en entrée contenant des données de produits afin de les charger dans une base de données. Le fichier CSV est le suivant :

products.csv
Sélectionnez

#id,name,description,price,published,lastUpdate
1,product1,description1,2500,true,2014-01-01
2,product2,description2,2400,true,2014-01-01

Imaginons que nous avons une classe Product qui représente la vue objet d'une ligne CSV :

Product.java
Sélectionnez

public class Product {

    private int id;
    private String name;
    private String description;
    private double price;
    private boolean published;
    private Date lastUpdate;
    //getters et setters omis

}

Le but est de créer une instance du type Product pour chaque ligne CSV, puis de stocker le produit dans la base de données (avec un PersistenceManager JPA par exemple). Avant de conserver les données en base, il faudra les valider afin de vérifier que :

  1. L'identifiant du produit n'est pas nul ;
  2. Le nom du produit n'est pas vide ;
  3. Le prix du produit n'est pas négatif ;
  4. La date de mise à jour du produit est dans le passé.

Dans cet article, pour rester simple, nous allons plutôt afficher les données des produits dans la console au lieu de les stocker en base de données. Enfin, nous voulons avoir à la fin d'exécution du batch un rapport précisant les lignes traitées avec succès et avec erreurs. Voici donc ce que nous avons à faire afin d'implémenter tous ces besoins :

  1. Lire le fichier CSV ligne par ligne ;
  2. Filtrer la ligne d'entête contenant les noms des champs ;
  3. Mapper chaque ligne à une instance du type Product ;
  4. Valider les données de produit ;
  5. Stocker le produit en base de données ;
  6. Générer un rapport d'exécution.

Avant de continuer, notons que dans tout ce qu'il y a à faire ci-dessus, seul le stockage des données en base représente la logique métier de notre application. Tout le reste peut être délégué à un outil (Easy Batch en l'occurrence) en fournissant quelques métadonnées de configuration (logique de mapping objet, contraintes de validation des données, etc.). Voyons donc comment utiliser Easy Batch pour traiter ce genre de besoin. Allez, top chrono !

II-A. Lecture du fichier

Notre besoin est de lire les données d'un fichier plat. Pour cela, il suffit d'utiliser le FlatFileRecordReader conçu spécialement pour ça :

 
Sélectionnez

Job job = JobBuilder.aNewJob()
            .reader(new FlatFileRecordReader("products.csv"))
            .build();

Le FlatFileRecordReader va lire les données et produire des instances du type StringRecord contenant les lignes CSV comme payload.

II-B. Filtrage de la ligne d'entête

La première ligne du fichier ne contient pas de données utiles, nous allons devoir la filtrer. Pour ce faire, on peut utiliser un RecordFilter. Dans ce cas, le HeaderRecordFilter permet de filtrer le premier record :

 
Sélectionnez

Job job = JobBuilder.aNewJob()
            .reader(new FlatFileRecordReader("products.csv"))
            .filter(new HeaderRecordFilter())
            .build();

II-C. Mapping des données à des objets de type Product

Pour mapper chaque ligne CSV à une instance de type Product, on utilise le DelimitedRecordMapper :

 
Sélectionnez

Job job = JobBuilder.aNewJob()
            .reader(new FlatFileRecordReader("products.csv"))
            .filter(new HeaderRecordFilter())
            .mapper(new DelimitedRecordMapper(Product.class, "id", "name", "description", "price", "published", "lastUpdate"))
            .build();

Par défaut, le DelimitedRecordMapper utilise la virgule comme séparateur, ce qui nous convient dans cet exemple. Nous devons cependant spécifier le type d'objet cible ainsi que la liste des champs dans l'ordre. Noter qu'Easy Batch fait aussi la conversion des données en fonction du type du champ cible. La stratégie de mapping est configurable et peut être adaptée au besoin.

II-D. Validation des données

Pour valider les données, Easy Batch fournit une implémentation de l'interface RecordValidator qui utilise l'API Bean Validation (JSR303) afin de valider les données. Cette API est très élégante dans le sens où elle nous permet de déclarer les contraintes de validation et non de les implémenter. Pour valider les données de produit comme demandé, il suffit donc d'annoter le type Product avec les annotations de l'API Bean Validation :

 
Sélectionnez

public class Product {

    @NotNull
    private long id;

    @NotEmpty
    private String name;

    private String description;

    @Min(0)
    private double price;

    private boolean published;

    @Past
    private Date lastUpdate;

    // getters et setters omis

}

Puis d'enregistrer le BeanValidationRecordValidator dans la définition du job Easy Batch :

 
Sélectionnez

Job job = JobBuilder.aNewJob()
            .reader(new FlatFileRecordReader("products.csv"))
            .filter(new HeaderRecordFilter())
            .mapper(new DelimitedRecordMapper(Product.class, "id", "name", "description", "price", "published", "lastUpdate"))
            .validator(new BeanValidationRecordValidator<Product>())
            .build();

II-E. Implémentation de la logique métier

À cette étape, il faudra quand même écrire un peu de code :-) (ce qu'Easy Batch ne peut pas deviner). La logique métier de notre application veut que l'on stocke les données en base de données, mais comme je l'ai dit un peu plus haut, pour rester simple, nous allons juste afficher dans la console les données du produit traité. Pour cela, il suffit d'implémenter l'interface RecordProcessor :

 
Sélectionnez

public class ProductProcessor implements RecordProcessor<Record, Record> {

    public Record processRecord(Record record) throws RecordProcessingException {
        System.out.println(record.getPayload());
        return record;
    }

}

Puis d'enregistrer notre ProductProcessor :

 
Sélectionnez

Job job = JobBuilder.aNewJob()
            .reader(new FlatFileRecordReader("products.csv"))
            .filter(new HeaderRecordFilter())
            .mapper(new DelimitedRecordMapper(Product.class, "id", "name", "description", "price", "published", "lastUpdate"))
            .validator(new BeanValidationRecordValidator<Product>())
            .processor(new ProductProcessor())
            .build();

II-F. Générer un rapport d'exécution

Pour générer un rapport d'exécution, nous n'avons rien à faire, Easy Batch se charge du reporting. Nous pouvons récupérer le résultat du job à la fin de l'exécution dans une instance du type JobReport :

 
Sélectionnez

JobReport report = JobExecutor.execute(job);

Le rapport d'exécution contient plusieurs informations, entre autres :

  1. La source des données traitées ;
  2. Les lignes ignorées, les lignes traitées avec erreurs (avec la cause de l'erreur) et les lignes traitées avec succès ;
  3. Le temps total d'exécution ;
  4. Etc.

Voilà, nous avons fini notre implémentation avec Easy Batch. Comme vous l'avez vu, nous n'avons implémenté que la logique métier de notre application, Easy Batch nous a déchargé de tout le reste, à savoir :

  1. Lecture des données ligne par ligne ;
  2. Filtrage des données inutiles ;
  3. Parsing des données CSV ;
  4. Mapping des données à des instances de notre objet de domaine Product ;
  5. Validation des données ;
  6. Génération d'un rapport d'exécution.

III. Conclusion

Dans cet article, nous avons vu comment le framework Easy Batch peut faciliter le développement de batchs en Java, en déchargeant le développeur des tâches fastidieuses et en le laissant se concentrer sur la logique métier de son application.

Finalement, à part la logique métier de l'application, nous n'avons écrit qu'une dizaine de lignes de code pour configurer un job Easy Batch. En réalité, sans Easy Batch, nous aurions écrit une centaine de lignes de code pour implémenter les besoins ci-dessus. Le gain est donc considérable, non seulement en termes de temps de développement, mais aussi de maintenabilité, de possibilité de réutilisation et de lisibilité du code.

Le code source de cet article peut être retrouvé icicode source de l'article. Pour exécuter le tutoriel, il suffit de lancer la commande Maven suivante à la racine du projet :

 
Sélectionnez

mvn package exec:java -PrunProductJob

Je tiens à remercier Mickael Baronhttp://mbaron.developpez.com/, Thierry Leriche-Dessirierhttp://thierry-leriche-dessirier.developpez.com/ et Cédric Duprezhttp://cedric-duprez.developpez.com/ pour leur relecture de cet article et leur participation à son amélioration.

IV. Annexe - Comparaison avec Spring Batch et la JSR 352

Spring Batch est aujourd'hui la référence pour le traitement des données par lots en Java. La popularité de ce framework a conduit à la standardisation de ses concepts dans la JSR 352 (Batch Applications for the Java Platform) de l'API Java EE 7. Spring Batch est très riche en fonctionnalités mais a une courbe d'apprentissage importante et n'est pas toujours facile à mettre en place. Il arrive même que sa mise en place soit plus compliquée que la résolution du problème !

Easy Batch, de son côté, se veut une alternative plus légère qui n'a pas vocation à concurrencer Spring Batch, mais qui peut être utilisée lorsque Spring Batch s'avère une solution lourde et coûteuse pour le problème à résoudre. Dans cette annexe, je vais essayer de comparer objectivement les deux frameworks en me basant sur les critères suivants :

  Easy Batch Spring Batch / Java EE 7 JSR 352
Implémente la JSR 352 Non Oui
Taille de la librairie 40Ko 543Ko
Courbe d'apprentissage Faible Grande
Développement basé sur des Pojos Oui Oui
Partitionnement de données Oui Oui
Supervision en temps réel Oui Oui
Gestion des transactions Programmatique Programmatique, Déclarative
Reprise sur erreur Non Oui
Administration à distance Non Oui
Traitement par lot (chunk) Oui Oui
Traitement en parallèle Oui Oui
Traitement asynchrone Oui Oui
Configuration de Job Java Java, Xml, annotation
Ordonnancement de Job Oui Oui

La figure 4 illustre la position d'Easy Batch entre Spring Batch et une solution " Partir de zéro " :

Image non disponible
Figure 4 : Easy Batch versus Spring Batch

En termes de fonctionnalités, Spring Batch (et les implémentations de la JSR 352) fournit plus de fonctionnalités avancées (remoting, flows, etc.).

Par contre, Easy Batch est une solution plus légère et plus facile à apprendre et à mettre en place. Noter aussi que les amateurs de la nouvelle API Batch auront besoin de monter un serveur d'application pour pouvoir traiter un fichier plat :-). Cette idée est choquante pour pas mal de gens et Easy Batch n'a pas été conçu pour ce mode de fonctionnement.

Pour conclure cette comparaison, Easy Batch est une solution intermédiaire entre Spring Batch et la solution de partir de zéro pour développer un batch en Java. Vous pouvez aussi trouver une comparaison complète entre Spring Batch et Easy Batch avec un exemple concret dans ce postspring-batch-vs-easy-batch-a-hello-world-comparison.

V. Références

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2014 Mahmoud Ben Hassine. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.