Cet article va faire halluciner certains collègues, car je vais parler de Java… Et même en dire du bien 😂… Pas trop quand même, sinon je vais devoir publier 2 articles sur python pour compenser…
Disclaimer
Je vais commencer par un aveu : je ne suis ni un croyant, ni un grand pratiquant du monde Java. Dans les faits (vie pro), il m’arrive tout de même d’en faire de temps en temps. Mais naturellement (vie perso), je vais plutôt vers Python, et ensuite vers PHP, Node.js ou d’autres écosystèmes du même genre.
Quand j’ai suivi ma formation Kafka il y a quelques années, j’ai eu une petite surprise. Dans la salle, j’étais le seul « pythoniste« . Donc forcément, j’ai fait les exercices en Python pendant que la majorité des autres les faisaient en Java.
Et sans vouloir relancer une guerre inutile, mon code était souvent plus court, plus direct et plus simple à lire. J’aime bien troller les collègues javistes avec ça, je l’assume assez volontiers. D’ailleurs pour ceux qui voudraient imaginer un monde sans Java, il y a une vidéo très drôle qui l’explique 😉.
Bon, après, il faut aussi être honnête deux minutes : en entreprise, dans beaucoup de contextes, Java reste la norme. Et Spring est souvent l’outil qui transforme cette norme en standard de fait.
De mon côté (vie perso), j’ai plutôt tendance à aimer les frameworks plus petits ou plus sobres. Des trucs comme Flask, Cement (tous les 2 utilisés au sein de SeedboxSync) ou Nest. Ça me parlent davantage parce qu’on voit mieux la mécanique. Spring, à l’inverse, arrive avec un gros écosystème, beaucoup d’intégration, beaucoup d’abstraction, et globalement quelque chose de plus lourd.
Pour autant, je ne vais pas faire semblant de découvrir le sujet : Spring a largement fait ses preuves. Dans des équipes nombreuses, avec des standards communs, des socles techniques partagés et des contraintes d’industrialisation, c’est un choix qui se défend très bien. Quand une organisation investit sérieusement dessus, avec un framework maison et un peu de rigueur, ça fonctionne même très bien.
Le sujet pour moi n’est donc pas vraiment de savoir s’il faut utiliser Spring ou non.
Le sujet, c’est plutôt de savoir jusqu’où on accepte qu’une couche d’abstraction masque le fonctionnement réel de Kafka (oui, car ont focus sur Kafka) ? Et c’est là que j’ai plus de réserves. En gros, ma position est assez simple : Spring, pourquoi pas. Spring for Apache Kafka, beaucoup moins automatiquement.
Pourquoi j’ai du mal avec Spring Kafka
Kafka a déjà sa propre complexité. Ses offsets, ses partitions, sa boucle de poll, ses commits, ses rebalances, ses garanties de livraison, ses retries, ses transactions… bref, ce n’est pas juste “un broker de messages” qu’on branche et qu’on oublie derrière une annotation.
Et c’est justement ce qui me gêne parfois avec Spring Kafka : on peut très vite faire fonctionner quelque chose sans réellement comprendre ce qui se passe dessous.
On produit avec un KafkaTemplate (et non un KafkaProducer), on consomme avec un @KafkaListener (et non un KafkaConsumer), on configure deux ou trois propriétés, et ça tourne. Tant mieux, d’une certaine manière. Sauf qu’à force, certains développeurs finissent par raisonner surtout avec le vocabulaire Spring, et beaucoup moins avec le vocabulaire Kafka.
Et à mon avis, ça se paie tôt ou tard.
Parce que quand tout va bien, franchement, ça passe… Mais dans la vraie vie il y a toujours ce cas qu’on sait qui n’arrivera « presque » jamais, mais qui finit très vite par arriver.
Donc quand on commence à parler de commits, de duplicats, de reprocessing, de rebalancing, de garanties de livraison, de lag, de traitement idempotent ou de diagnostic en production, là il vaut mieux savoir ce que fait réellement le client Kafka !
Le dernier exemple en date : enable.auto.commit=false
C’est le dernier exemple qui m’ait convaincu et que je trouve parlant.
Avec le client Java Apache Kafka natif, enable.auto.commit=false, le message est assez clair : le client ne commit plus automatiquement les offsets. Donc si tu veux commit, c’est à toi de décider quand et comment tu le fais avec commitSync() ou commitAsync().
Et ça, j’aime bien, parce que c’est explicite.
Avec Spring Kafka, c’est plus subtil. Le paramètre désactive bien l’auto-commit côté client Kafka, mais tant que tu restes dans le modèle de container Spring Kafka, tu ne récupères pas pour autant un contrôle direct équivalent au client natif. Le container peut continuer à piloter les commits selon l’AckMode choisi.
Autrement dit, mettre enable.auto.commit=false ne veut pas dire, à lui seul, « je reprends complètement la main ». Alors oui, vous allez me dire qu’il existe AckMode.MANUAL, et c’est vrai. Mais c’est précisément mon point : pour aller chercher un contrôle fin, il faut combiner enable.auto.commit=false avec AckMode.MANUAL, ce qui oblige à jongler avec une deuxième couche de configuration Spring en plus des concepts Kafka.
C’est précisément le genre de détail qui peut piéger. Quelqu’un peut penser avoir repris le contrôle des offsets alors qu’il est encore dans une logique de commit pilotée par Spring : on n’est déjà plus dans un modèle aussi lisible que le client natif.
Ce que j’aime bien avec le client Java natif
Déjà il est proche du client Python 😉. Ensuite, le gros avantage du client Kafka natif, c’est qu’il montre la mécanique.
Quand on lit du code avec KafkaProducer et KafkaConsumer, on voit tout de suite où sont les responsabilités. On voit comment on produit, comment on consomme, quand on commit, comment on gère les erreurs, ce qu’on fait pendant un rebalance, jusqu’où on pousse les retries, et où s’arrête la responsabilité du framework applicatif.
Oui, le code est souvent plus verbeux, mais il est aussi plus honnête. Et pour moi, dans Kafka, ce n’est pas un détail.
Le client natif force un peu plus à comprendre ce qu’on fait. Je trouve ça sain, surtout sur des sujets où les garanties de traitement comptent vraiment.
Je ne dis pas non plus que Spring Kafka ne sert à rien
Je préfère le préciser, parce que sinon je vais encore passer pour le type qui tape sur Spring juste pour le plaisir (alors que je tape sur Java au global 😄).
Spring Kafka a des choses utiles. Notamment tout ce qui tourne autour de la gestion d’erreurs, des retries, des DLT (on pourrait discuter du fait que c’est un peu trop automatique, mais je vais m’arrêter là…) et de certains comportements de container. Pour des équipes qui veulent industrialiser vite, homogénéiser les pratiques et rester dans un cadre Spring cohérent, ça a du sens.
Il y a par exemple authExceptionRetryInterval, que je trouve plutôt intéressant. L’idée est simple : au lieu d’arrêter immédiatement le container sur une AuthenticationException ou une AuthorizationException, on peut attendre un peu et réessayer. Dans un contexte où l’authentification dépend d’un serveur OAuth qui tousse un peu, c’est typiquement le genre de détail qui rend un service plus robuste (OK, c’est aussi au serveur d’authentification d’être robuste, mais des fois, il est dans les mains d’autres équipes…).
Donc non, mon propos n’est pas de dire que Spring Kafka est nul.
Mon propos est plutôt de dire que les choses utiles qu’il apporte ont un coût : une couche d’abstraction en plus, un vocabulaire en plus, et parfois une compréhension un peu moins nette de ce qui relève réellement de Kafka.
Là où je mets une limite
Dans une application Spring, utiliser Spring pour la configuration, l’injection de dépendances, l’intégration avec le reste du SI ou la structuration globale de l’application, ça ne me pose aucun problème.
Là où je deviens plus prudent, c’est quand on touche à des consommateurs un peu sensibles, à des stratégies de commit importantes, à des garanties de livraison qu’il faut vraiment comprendre, ou à des flux métier où le diagnostic en production peut devenir pénible.
Dans ces cas-là, j’ai souvent plus confiance dans le client Java Kafka natif.
Pas parce qu’il serait plus élégant. Clairement, il ne l’est pas toujours.
Mais parce qu’il me laisse voir la mécanique plus directement.
Et sur Kafka, voir la mécanique, je trouve que c’est rarement du temps perdu.
Et vous ?
Et vous, dans vos projets Spring, vous faites quoi ? Vous utilisez Spring Kafka sans trop vous poser de questions ? Vous restez au client natif ? Ou vous mélangez les deux selon les cas ?




