Guide principal : Apache Kafka en production en 2026 : panorama complet
Tutoriels reliés : Producers et consumers idempotents · Pipeline CDC PostgreSQL avec Debezium
Quand un cluster Kafka grossit, le premier symptôme de douleur n’est pas la latence ou le débit — c’est le contrat de données. Une équipe ajoute un champ à un événement, oublie de prévenir les consommateurs, et la moitié du SI tombe. Un Schema Registry règle ce problème en imposant une discipline : tout producer doit enregistrer le schéma de ses messages, et tout consumer reçoit la garantie que le schéma sera compatible avec celui qu’il connaît. Confluent Schema Registry est la référence du marché, intégrée nativement à la Confluent Platform 8.2 (livrée avec Kafka 4.2), tandis que Karapace d’Aiven et Apicurio de Red Hat offrent des alternatives open-source plus légères. Ce tutoriel utilise Confluent Schema Registry avec Avro et un cluster Kafka 4.2 auto-hébergé.
À noter : si vous cherchez une vue plus large sur les arbitrages Avro vs Protobuf vs JSON Schema en mode broker-neutre, consultez aussi l’article Schema Registry et évolution des contrats d’événements du blog. Le présent tutoriel se concentre spécifiquement sur l’intégration côté Apache Kafka 4.2 + Confluent SR + serdes Java.
Prérequis
- Un cluster Kafka 4.2 fonctionnel (voir tutoriel précédent)
- Docker 27+ ou Java 21 pour exécuter Confluent Schema Registry
- Maven 3.9 ou Gradle 8.10 pour le projet Java
- Connaissances de base en JSON et idée de ce qu’est un schéma typé
- Temps estimé : 80 minutes
Étape 1 — Comprendre Avro et le contrat de données
Avro est un format de sérialisation binaire compact développé à l’origine par Doug Cutting au sein de Hadoop. Il diffère de JSON par trois caractéristiques majeures : il est binaire (les nombres et booléens prennent quelques bits, pas plusieurs octets ASCII), il est strictement typé (chaque champ a un type déclaré et une nullabilité explicite), et il sépare le schéma des données (le schéma n’est pas répété à chaque message comme c’est le cas en JSON). Pour un événement de paiement mobile typique, un message Avro pèse 40 à 60 % moins que son équivalent JSON, et le décodage est environ deux fois plus rapide côté CPU.
Mais le vrai bénéfice d’Avro n’est pas la taille — c’est la compatibilité dans le temps. Avro distingue trois modes de compatibilité d’évolution de schéma. Backward compatibility : un nouveau schéma peut lire des messages écrits avec l’ancien (typiquement, on ajoute un champ optionnel). Forward compatibility : l’ancien schéma peut lire des messages écrits avec le nouveau (typiquement, on supprime un champ optionnel). Full compatibility : les deux à la fois. Le Schema Registry rejette les évolutions de schéma qui cassent la compatibilité configurée — c’est précisément cela qui empêche un changement amont de faire planter les consommateurs.
Étape 2 — Démarrer Confluent Schema Registry
Le moyen le plus rapide d’installer Confluent Schema Registry est via le conteneur officiel Docker. La version 8.2.0 alignée sur Kafka 4.2 est publiée sur Docker Hub.
# docker-compose.yml
services:
schema-registry:
image: confluentinc/cp-schema-registry:8.2.0
container_name: schema-registry
restart: unless-stopped
ports:
- "8081:8081"
environment:
SCHEMA_REGISTRY_HOST_NAME: schema-registry
SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1.example.com:9092,PLAINTEXT://kafka2.example.com:9092
SCHEMA_REGISTRY_SCHEMA_COMPATIBILITY_LEVEL: BACKWARD
docker compose up -d schema-registry
curl -s http://localhost:8081/subjects
# Sortie attendue : [] (aucun sujet pour l'instant)
Schema Registry est un service stateless qui stocke ses données dans un topic interne du cluster Kafka, nommé par défaut _schemas. C’est important à comprendre : si vous perdez Kafka, vous perdez les schémas. La protection se fait par la réplication du topic (facteur 3 par défaut sur Confluent Platform). Le port 8081 sert l’API REST avec laquelle producers et consumers vont interagir. La variable d’environnement SCHEMA_REGISTRY_SCHEMA_COMPATIBILITY_LEVEL=BACKWARD définit la politique par défaut ; on peut la surcharger par sujet à tout moment.
Étape 3 — Définir un schéma Avro pour les paiements
Un schéma Avro est un document JSON qui décrit la structure d’un type. Le fichier ci-dessous représente un événement de paiement mobile-money tel qu’on l’utiliserait sur une plateforme sénégalaise ou ivoirienne.
// src/main/avro/Payment.avsc
{
"namespace": "io.itskillscenter.kafka.avro",
"type": "record",
"name": "Payment",
"fields": [
{ "name": "id", "type": "string", "doc": "Identifiant unique du paiement" },
{ "name": "client", "type": "string", "doc": "Numéro international du client" },
{ "name": "montant", "type": "long", "doc": "Montant en centimes FCFA" },
{ "name": "moyen", "type": { "type": "enum", "name": "MoyenPaiement",
"symbols": ["WAVE", "ORANGE_MONEY", "MTN_MOMO", "MOOV_MONEY", "MIXX_BY_YAS"] } },
{ "name": "timestamp", "type": { "type": "long", "logicalType": "timestamp-millis" } },
{ "name": "marchand_id", "type": ["null", "string"], "default": null }
]
}
Trois détails à observer. "type": "long" avec logicalType: timestamp-millis indique à Avro de traiter le champ comme une date — la valeur stockée reste un entier 64 bits, mais les serdes savent la convertir en Instant côté Java. L’union ["null", "string"] avec default: null rend le champ optionnel et stable pour les évolutions futures (on pourra retirer le champ sans casser les anciens consumers). Enfin, l’enum sur les moyens de paiement évite les fautes de frappe et impose un vocabulaire stable au niveau du contrat.
Étape 4 — Générer les classes Java depuis le schéma
Le plugin Maven Avro génère automatiquement une classe POJO depuis le fichier .avsc. Cela évite d’écrire et de maintenir la classe à la main — chaque modification du schéma met à jour le code Java au prochain compile.
<!-- pom.xml -->
<dependency>
<groupId>io.confluent</groupId>
<artifactId>kafka-avro-serializer</artifactId>
<version>8.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.12.0</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.apache.avro</groupId>
<artifactId>avro-maven-plugin</artifactId>
<version>1.12.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals><goal>schema</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>confluent</id>
<url>https://packages.confluent.io/maven/</url>
</repository>
</repositories>
Un mvn compile génère le fichier Payment.java dans target/generated-sources/avro. La classe est immutable et thread-safe ; elle expose un builder fluent (Payment.newBuilder().setId(...).build()) qui valide automatiquement la conformité au schéma. Le dépôt Confluent doit être déclaré explicitement car les artefacts kafka-avro-serializer ne sont pas sur Maven Central — ils résident sur packages.confluent.io.
Étape 5 — Producer Avro avec enregistrement automatique
Le serializer Confluent gère tout en arrière-plan : il enregistre le schéma au Schema Registry lors du premier envoi, récupère l’identifiant retourné, et préfixe chaque message avec cet identifiant (4 octets) plus le marqueur magique (1 octet). Au décodage, le consumer demande au Registry le schéma correspondant à l’identifiant et procède à la désérialisation.
package io.itskillscenter.kafka.avro;
import io.confluent.kafka.serializers.KafkaAvroSerializer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.time.Instant;
import java.util.Properties;
public class AvroPaymentProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1.example.com:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
props.put("schema.registry.url", "http://localhost:8081");
try (KafkaProducer producer = new KafkaProducer<>(props)) {
Payment paiement = Payment.newBuilder()
.setId("PAY-2026-05-17-001")
.setClient("+221771234567")
.setMontant(1500000L) // 15 000 FCFA en centimes
.setMoyen(MoyenPaiement.WAVE)
.setTimestamp(Instant.now().toEpochMilli())
.setMarchandId("BOUTIQUE-ABIDJAN-42")
.build();
ProducerRecord record =
new ProducerRecord<>("paiements-avro", paiement.getClient().toString(), paiement);
producer.send(record).get();
System.out.println("Paiement publié: " + paiement.getId());
} catch (Exception e) { e.printStackTrace(); }
}
}
Au premier send, le client Confluent appelle POST /subjects/paiements-avro-value/versions sur le Registry, qui retourne {"id": 1}. Tous les messages suivants sur ce topic réutilisent l’identifiant 1. On peut vérifier l’enregistrement avec curl http://localhost:8081/subjects : la sortie inclut désormais "paiements-avro-value". Le suffixe -value est la convention Confluent pour le schéma de la valeur ; un -key apparaîtrait si on sérialisait aussi la clé en Avro.
Étape 6 — Consumer Avro et désérialisation typée
Côté consumer, on configure le KafkaAvroDeserializer avec l’URL du Registry et le flag specific.avro.reader=true pour récupérer une instance typée de Payment au lieu du GenericRecord générique. C’est ce flag qui fait la différence entre du code propre et un dictionnaire de champs nommés à manipuler à la main.
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1.example.com:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "comptable-paiements-v1");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class.getName());
props.put("schema.registry.url", "http://localhost:8081");
props.put("specific.avro.reader", "true");
try (KafkaConsumer consumer = new KafkaConsumer<>(props)) {
consumer.subscribe(List.of("paiements-avro"));
while (true) {
ConsumerRecords recs = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord r : recs) {
Payment p = r.value();
System.out.printf("%s -> %d FCFA via %s%n",
p.getId(), p.getMontant() / 100, p.getMoyen());
}
}
}
Le consumer reçoit directement des objets Payment avec leurs getters typés. getMoyen() renvoie un MoyenPaiement, pas une chaîne — l’autocompletion de l’IDE expose tous les symboles autorisés. Si l’IDE ne reconnaît pas la classe, vérifier que target/generated-sources/avro est ajouté aux source folders du projet ; Maven le fait automatiquement via le plugin Avro.
Étape 7 — Faire évoluer un schéma sans casser les consommateurs
Six mois après le lancement, le métier demande d’ajouter un champ devise aux paiements pour distinguer FCFA, USD et EUR. Si on ajoute le champ sans précaution, les anciens consommateurs ne sauront pas lire les nouveaux messages — ou pire, l’enregistrement du nouveau schéma sera rejeté par le Registry. La règle simple : un nouveau champ doit avoir une valeur par défaut, ce qui le rend backward-compatible.
// Payment.avsc — version 2
{
...
"fields": [
... // champs existants
{ "name": "devise", "type": "string", "default": "XOF" }
]
}
Le default: "XOF" indique au schéma reader : si un message arrive sans ce champ (parce qu’écrit avec la v1), considère qu’il vaut XOF. Le Registry accepte cet enregistrement comme évolution backward. À l’inverse, supprimer un champ obligatoire ou en changer le type provoquera un rejet HTTP 409 avec le détail « Schema being registered is incompatible with an earlier schema ». C’est précisément cette barrière qui protège la production.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| HTTP 409 à l’enregistrement | Évolution non compatible | Ajouter un default ou changer la politique du sujet en NONE |
SerializationException: Unknown magic byte |
Topic mélange messages Avro et JSON | Créer un nouveau topic dédié à Avro |
RestClientException: 401 |
Registry protégé par auth basic non configurée | Ajouter basic.auth.credentials.source=USER_INFO et schema.registry.basic.auth.user.info=user:pass |
| Topic existe mais sujet absent dans le Registry | Le producer n’a jamais publié | Forcer un premier envoi ou enregistrer manuellement le schéma via curl |
| Latence élevée au démarrage | Cache du client Schema Registry froid | Activer auto.register.schemas=false en prod et précharger les schémas au boot |
Alternatives open-source : Karapace et Apicurio
Confluent Schema Registry impose la Confluent Community License, qui interdit explicitement la revente du service sous forme de SaaS managé. Pour une PME qui auto-héberge son cluster, ce n’est pas un problème — la licence permet l’usage en interne, y compris commercial. Mais pour qui veut une vraie licence Apache 2.0, deux alternatives sont matures en 2026. Karapace, développé par Aiven, est un drop-in en Python compatible API jusqu’à la version 6.1.1 de Confluent SR ; il consomme moins de mémoire et démarre plus vite. Apicurio Registry, soutenu par Red Hat, est plus généraliste (gère aussi les schémas OpenAPI, AsyncAPI, Protobuf) et persiste les données dans PostgreSQL plutôt que dans un topic Kafka, ce qui simplifie la sauvegarde.
Le choix se fait au cas par cas : Confluent SR si vous êtes déjà sur Confluent Platform, Karapace si vous voulez le minimum de surface, Apicurio si vous gérez plusieurs types de schémas hors Kafka. Le client kafka-avro-serializer de Confluent reste compatible avec les trois — c’est la même API REST sous-jacente.
Schéma et conformité réglementaire
Au Sénégal, en Côte d’Ivoire ou au Mali, la loi sur la protection des données personnelles impose un contrôle strict sur les champs sensibles transmis dans les flux. Le Schema Registry est un outil de gouvernance très pratique pour matérialiser cette contrainte : on documente dans le schéma quels champs contiennent des données personnelles (numéro de téléphone, identifiant national) via la propriété doc ou des balises Avro custom. À la revue de schéma, on peut alors imposer une politique automatisée — chiffrement des champs sensibles, durée de rétention courte, droit d’accès limité aux consumers autorisés.
Coût et infrastructure
Schema Registry est un service léger : un conteneur tourne avec 256 Mo de RAM et moins de 0,1 vCPU en charge normale. Sur un VPS Hetzner CX22 à 4,49 euros par mois, on peut faire tourner Schema Registry, Kafka Connect et un cluster Kafka monoinstance pour environnement de développement. En production, on coloce souvent le Schema Registry sur la machine d’administration Kafka, ce qui ne consomme pas de ressources additionnelles significatives. Le topic _schemas reste minuscule — quelques mégaoctets pour une centaine de sujets — et la rétention est compact, donc pas de croissance dans le temps.
Ressources et références
- Documentation officielle Confluent Schema Registry (Platform 8.2)
- Spécification Apache Avro 1.12.0
- Karapace — alternative open-source Apache 2.0
- Apicurio Registry — registry multi-schémas Red Hat
- Article complémentaire — Schema Registry et compatibilité (broker-neutre)
Subject naming strategy : par topic, par record, ou contexte
Le Schema Registry organise les schémas en subjects. Trois stratégies de nommage coexistent. TopicNameStrategy (par défaut) crée un sujet par topic, sous la forme <topic>-value et <topic>-key. Simple, lisible, mais cela impose qu’un topic ne contienne qu’un seul type d’événement. RecordNameStrategy nomme le sujet d’après le namespace.name du schéma Avro — utile quand un topic agrège plusieurs types d’événements liés. TopicRecordNameStrategy combine les deux, ce qui isole les schémas par topic tout en autorisant la multiplicité de types.
En pratique, la stratégie par défaut suffit dans 80 % des cas. Le passage à RecordNameStrategy ne se justifie que pour des architectures événementielles avancées où un topic commandes peut transporter des CommandeCreee, CommandeAnnulee et CommandeLivree dans la même partition pour préserver l’ordre. On configure la stratégie côté producer via value.subject.name.strategy=io.confluent.kafka.serializers.subject.RecordNameStrategy.
Audit, lineage et data contracts en 2026
Confluent Platform 8.2 introduit deux mécanismes qui complètent le Schema Registry. Les data contracts, sortis en GA en 2024, permettent d’attacher au schéma des règles métier (par exemple « le champ montant doit être positif et inférieur à 10 millions »), des règles de migration (transformation automatique lors d’une rupture de compatibilité), et des règles de gouvernance (qui peut publier, qui peut consommer). Les Schema IDs in Kafka headers, ajoutés en Platform 8.2, déplacent l’identifiant du schéma du payload vers les headers du message — un message Avro plus compact, et une indépendance entre le format et la déclaration de schéma.
Pour une équipe ouest-africaine qui démarre, ces fonctionnalités avancées ne sont pas immédiatement nécessaires. Le Schema Registry de base, avec compatibility BACKWARD et auto-register activé en dev / désactivé en prod, couvre déjà 95 % du besoin. Les data contracts deviennent intéressants au-delà de cinquante équipes consommatrices d’un même flux ou lors d’une migration de plateforme.
Production : ce qu’il faut désactiver
En développement, auto.register.schemas=true est pratique : on code, on lance, le schéma se publie tout seul. En production, c’est exactement ce qu’il ne faut pas faire. Si un développeur déploie un code mal relu qui publie un schéma incompatible, le Registry l’enregistre tel quel et casse les consumers. La pratique recommandée est auto.register.schemas=false côté producer et un workflow d’enregistrement explicite via la CLI schema-registry-maven-plugin ou un pipeline GitHub Actions qui valide la compatibilité avant le merge.
# .github/workflows/schema-check.yml
on: pull_request
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Test compatibility
run: |
curl -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" \
--data @schema-payload.json \
https://schema-registry.itskillscenter.io/compatibility/subjects/paiements-avro-value/versions/latest
Le retour {"is_compatible": true} indique que la nouvelle version peut être publiée sans casser les consumers existants. Un false bloque le merge automatiquement. Cette pratique simple supprime à elle seule 90 % des incidents de production liés aux schémas.
Retour au guide principal : Apache Kafka en production en 2026 : panorama complet.
Mise en production : avant le go-live, durcir Wave Business API pour la prod.