Blog Zenika

#CodeTheWorld

Java

[Spring Batch] REX pour des batchs tombés trop tôt au combat

J’ai été naïf. J’ai espéré que tout se passerait bien. J’ai développé précipitamment plusieurs batchs puis les ai envoyés au combat sans filet, si jeunes. Ils sont tombés, tous. Le coût de ma naïveté a été terrible, mais j’aimerais ici faire le bilan, vous faire part de ce que j‘ai appris de cette aventure. Certains points vous paraitront peut-être évidents mais si je n’ai pas su les anticiper je suppose que d’autres pourraient les éviter en me lisant.

Cet article assume que vous avez déjà manipulé un minimum Spring Batch. Sa seule prétention est de vous proposer des pistes pour rendre vos batchs plus fiables.


Ne faites pas confiance aux autres SI

Toute donnée venant d’un SI externe doit se conformer à un contrat défini entre vous et ledit SI. N’ayant pas la main sur ces données, les contraintes définies au départ sont susceptibles de changer sans que vous ne soyez prévenus. Ça arrivera.

Il existe des schémas de validation de données pour énormément de formats : WSDL pour SOAP et JSON schema pour JSON par exemple. Pour valider des objets java vous pouvez utiliser simplement la librairie javax.validation. (Article : Java Bean Validation Basics )

Le principe du validateur est de vérifier que la donnée récupérée est conforme à ce que vous attendez. Quand ça n’est pas le cas, le validateur recense tous les champs non valides. Vous n’avez alors qu’à exclure la donnée en question et continuer le traitement pour les autres.

Avec Spring Batch, vous allez faire la validation dans un processor juste après le reader. Vous pourrez alors stocker les données invalides en BDD dans une table d’erreur pour pouvoir les analyser ultérieurement.

@Autowired
private Validator validator

public Data process(final Data input) {
    Set<ConstraintViolation<Data>> constraintViolations = validator.validate(input);

    if (constraintViolations.isEmpty()) {
        return input;
    } else {
        tableErreurRepository.save(data, constraintViolations);
        //En renvoyant null la donnée est ignorée.
        return null;
    }
}

Le validator @Autowired est un bean instancié dans un fichier @Configuration.

@Bean
public Validator validator() {
  return new LocalValidatorFactoryBean();
}

Eviter les NullPointerException

“Je cherche l’information D. Mais si ! Celle dans l’objet C qui est dans l’objet B. D’ailleurs B et D sont optionnels… Bref trouve moi ça.”

Ce genre de situation arrive souvent, je présente ici quelques pratiques que j’ai rencontrées, la dernière étant selon moi la meilleure :

  • La pratique la plus simple, mais la plus verbeuse.
if (A != null && A.getB() != null && A.getB().getC() != null) {
    return A.getB().getC().getD();
}
  • Définir une méthode dans A qui retourne directement D mais selon la loi de Déméter c’est une mauvaise pratique et si le cas se présente suffisamment souvent pour justifier de le faire c’est qu’il y a potentiellement un souci dans votre modèle.
  • Utiliser une méthode qui catch les NPEs pour renvoyer null. L’idée est laide et peut tout de même produire des NPEs. Même une valeur en sortie qui ne devrait pas être nullable le devient, je n’aime vraiment pas cette pratique.
/**
* ATTENTION ! int i = getSafeDeep(() -> A.getI()) enverra une NPE si la méthode renvoie null.
*/
public static <T> T getSafeDeep(Supplier<T> supplier) {
    try {
        return supplier.get();
    } catch (NullPointerException npe) {
        return null;
    }
}
  • La pratique la plus propre selon moi et la plus flexible avec toutes les possibilités qu’offrent Optional et Stream. Le map d’un Optional renvoie None si l’objet en sortie est null donc aucun risque de NPE.
Optional.ofNullable(A)
        .map(A::getB)
        .map(B::getC)
        .map(C::getD)
        .orElse(null) //or any other default value

Prévenir les erreurs

Comme pour tout développement les tests unitaires et d’intégration sont indispensables mais d’autres astuces permettent de s’assurer que vos batchs s’exécuteront correctement.

Le mode dryrun

J’ajoute systématiquement à chacun de mes batchs un paramètre d’entrée nommé dryrun. Ce paramètre dryrun est un boolean qui, s’il est activé, permet de lancer le batch “à blanc”. Ainsi je peux voir comment le traitement se passe sans impacter les données.

@Value("#{jobParameters['dryrun']?:true}")
private boolean dryrun;

public void write(List<? extends T> datas) throws Exception {
    if (!dryrun) {
        dataRepository.save(datas);
    }
}

Le paramètre dryrun est par défaut à true, car il vaut mieux ne pas modifier les données en oubliant de le désactiver que de modifier les données par erreur.

Pour avoir un contrôle sur les paramètres d’entrée, vous pouvez donner un validator à votre jobBuilder. DefaultJobParametersValidator devrait être suffisant dans la plupart des cas. Si vous avez besoin d’ajouter des contraintes entre les paramètres ou des valeurs par défaut alors il va falloir créer votre propre JobParametersValidator.

Encore en cours d’exécution ?

Vérifier que le batch n’est pas déjà en cours d’exécution peut s’avérer utile pour ne pas corrompre ses données. Une méthode simple est d’utiliser un script shell vérifiant dans les processus de la machine si le batch n’est pas déjà en cours d’exécution.

Une autre méthode est de programmer le batch pour qu’il ne se lance pas si un job ayant le même nom n’est toujours pas terminé. Cela devra se faire lors de l’exécution du JobParametersIncrementer où vous pourrez utiliser votre JobRepository pour requêter votre BDD.


Gérer les erreurs

Malgré la validation des données, il y aura toujours un moment où vos batchs tomberont en erreur (Oui, je suis passé de naïf à désabusé).

Réagir aux Exceptions

Il est possible que certaines opérations provoquent des exceptions. Baeldung possède un article très complet et clair sur l’utilisation du Skip : Configuring Skip Logic in Spring Batch. J’ajouterai seulement qu’il existe la même logique avec retry qui peut mieux convenir à d’autres cas.

Des alertes automatisées

Les listeners tels que SkipListener permettent de programmer le comportement du batch lorsqu’une Exception est skipped. À vous de décider si vous voulez simplement stocker l’information dans une table dédiée de votre BDD ou stocker en mémoire l’ensemble de ces exceptions pour envoyer un rapport (slack/email/autre) à la fin du traitement.


Analyser l’exécution d’un batch 

Via la BDD

Votre premier réflexe devrait être d’aller voir en BDD les conditions d’arrêt d’un batch. Les tables dédiées donnent le statut de sortie de l’exécution du batch et des informations détaillées sur le nombre de passages dans les divers strates du batch ou encore quel était le  jobExecutionContext et le stepExecutionContext.

Via les logs

Chaque exécution de chaque batch doit avoir son propre fichier de logs au risque de devoir chercher une aiguille dans une botte de foin. Pour éviter de prendre trop de place sur la machine, il vous faudra archiver (compression des logs vieux de plus de quelques jours) et purger (suppression des archives vieilles de plus de quelques semaines) ces logs via un autre script. N’hésitez pas à ajouter des logger.debug un peu partout dans le code pour pouvoir rapidement tester le batch si nécessaire (en l’activant soit via un fichier de properties ou via un paramètre debug). Le mode debug se couple bien avec le paramètre dryrun. 


Faites des batchs indépendants

Des batchs indépendants de l’application permettent des déploiements sans avoir à redéployer tout le projet. Si l’application arrête de fonctionner le lancement des batchs n’est pas impacté. Leur ordonnancement peut également être modifié facilement.

Pour gagner en performance, il est recommandé de déployer les batchs sur une autre machine que celle où l’application se situe pour que les processus ne consomment pas les même ressources et ne se gênent pas.


Conclusion

C’est en tombant que l’on apprend ! J’ai passé beaucoup de temps à corriger et améliorer mes batchs pour les rendre plus résilients, plus faciles à débugger. J’espère que ces quelques conseils vous permettront de passer directement aux problèmes suivants !


Retrouvez toutes nos formations officielles Pivotal

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Continue reading