Étape 1 : Installation et configuration de Kafka
1. Téléchargement de Kafka
Téléchargement de la dernière version stable depuis le site officiel Apache Kafka. Extraction de l’archive dans le répertoire d’installation.
2. Génération d’un cluster ID
Pour Kafka en mode KRaft (sans ZooKeeper), génération d’un identifiant unique de cluster :
sudo /usr/share/kafka/bin/kafka-storage.sh random-uuidRésultat obtenu :
Jp8Wc3R6QH2sKZyA9LxM5D
Cet identifiant sera utilisé pour formater les répertoires de stockage.
3. Formatage des répertoires de stockage
Initialisation du stockage Kafka avec l’ID de cluster généré :
sudo /usr/share/kafka/bin/kafka-storage.sh format -t Jp8Wc3R6QH2sKZyA9LxM5D -c /etc/kafka/server.properties --standaloneRésultat :
Formatting dynamic metadata voter directory /var/lib/kafka with metadata.version 4.1-IV1.
Attribution des permissions au dossier Kafka :
sudo chown -R kafka:kafka /var/lib/kafka4. Démarrage de Kafka
Lancement du service Kafka via systemd :
sudo systemctl start kafkaVérification du statut du service :
sudo systemctl status kafkaSortie obtenue :
kafka.service - Kafka server
Loaded: loaded (/usr/lib/systemd/system/kafka.service; disabled; preset: disabled)
Active: active (running) since Sat 2025-12-20 23:15:29 CET; 4s ago
Main PID: 229314 (java)
Tasks: 111 (limit: 18810)
Memory: 410.7M (peak: 411.8M)
CPU: 6.068s
Le service Kafka est actif et fonctionnel. Le processus Java consomme environ 410 MB de mémoire.
5. Création du topic commande-creee
Création d’un topic Kafka avec une partition et un facteur de réplication de 1 :
/usr/share/kafka/bin/kafka-topics.sh --create --topic commande-creee --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1Résultat :
Created topic commande-creee.
Vérification de la création du topic :
/usr/share/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092Liste des topics :
__consumer_offsets
commande-creee
Le topic commande-creee est bien créé. Le topic __consumer_offsets est créé automatiquement par Kafka pour gérer les offsets des consommateurs.
Étape 2 : Développement du service Produit
1. Création du projet Maven
Création d’un projet Spring Boot nommé produitService avec les dépendances suivantes dans pom.xml :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>2. Entité Produit
Création de l’entité Produit.java dans le package com.produit.model :
package com.produit.model;
import javax.persistence.*;
@Entity
@Table(name = "produits")
public class Produit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String nom;
@Column(nullable = false)
private double prix;
public Produit() {}
public Produit(String nom, double prix) {
this.nom = nom;
this.prix = prix;
}
// Getters et setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public double getPrix() {
return prix;
}
public void setPrix(double prix) {
this.prix = prix;
}
}3. Repository Produit
Création de ProduitRepository.java dans le package com.produit.repository :
package com.produit.repository;
import com.produit.model.Produit;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ProduitRepository extends JpaRepository<Produit, Long> {
}L’interface étend JpaRepository qui fournit toutes les opérations CRUD de base.
4. Contrôleur REST Produit
Création de ProduitController.java dans le package com.produit.controller :
package com.produit.controller;
import com.produit.model.Produit;
import com.produit.repository.ProduitRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/produits")
public class ProduitController {
@Autowired
private ProduitRepository produitRepository;
@GetMapping
public List<Produit> getAllProduits() {
return produitRepository.findAll();
}
@PostMapping
public ResponseEntity<Produit> createProduit(@RequestBody Produit produit) {
Produit savedProduit = produitRepository.save(produit);
return ResponseEntity.ok(savedProduit);
}
}Endpoints exposés :
GET /produits: Récupère la liste de tous les produitsPOST /produits: Crée un nouveau produit
5. Configuration
Fichier application.properties :
server.port=8082
spring.application.name=produit-service
# H2 Database
spring.datasource.url=jdbc:h2:mem:produitDB
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
# JPA
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
# H2 Console
spring.h2.console.enabled=true
spring.h2.console.path=/h2-consoleLe service Produit est configuré sur le port 8082 avec une base H2 en mémoire. La console H2 est accessible pour vérifier les données.
Classe principale ProduitServiceApplication.java :
package com.produit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProduitServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProduitServiceApplication.class, args);
}
}Étape 3 : Développement du service Commande
1. Création du projet Maven
Création du projet commandeService avec les dépendances Spring Boot Web, JPA, Kafka et H2 dans pom.xml :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>2. Entité Commande
Création de l’entité Commande.java dans le package com.commande.model :
package com.commande.model;
import javax.persistence.*;
@Entity
@Table(name = "commandes")
public class Commande {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long produitId;
@Column(nullable = false)
private int quantite;
public Commande() {}
public Commande(Long produitId, int quantite) {
this.produitId = produitId;
this.quantite = quantite;
}
// Getters et setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getProduitId() {
return produitId;
}
public void setProduitId(Long produitId) {
this.produitId = produitId;
}
public int getQuantite() {
return quantite;
}
public void setQuantite(int quantite) {
this.quantite = quantite;
}
}3. Repository Commande
Création de CommandeRepository.java dans le package com.commande.repository :
package com.commande.repository;
import com.commande.model.Commande;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CommandeRepository extends JpaRepository<Commande, Long> {
}4. Service Commande avec producteur Kafka
Création de CommandeService.java dans le package com.commande.service :
package com.commande.service;
import com.commande.model.Commande;
import com.commande.repository.CommandeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
@Service
public class CommandeService {
@Autowired
private CommandeRepository commandeRepository;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
private static final String TOPIC = "commande-creee";
public Commande createCommande(Long produitId, int quantite) {
Commande commande = new Commande(produitId, quantite);
Commande savedCommande = commandeRepository.save(commande);
// Publication d'un événement Kafka après la création
String message = "Commande ID: " + savedCommande.getId() +
", Produit ID: " + savedCommande.getProduitId() +
", Quantité: " + savedCommande.getQuantite();
kafkaTemplate.send(TOPIC, message);
return savedCommande;
}
}Rôle du service : Après avoir persisté la commande en base H2, le service publie un message sur le topic Kafka commande-creee. Le KafkaTemplate est injecté automatiquement par Spring Boot.
5. Contrôleur REST Commande
Création de CommandeController.java dans le package com.commande.controller :
package com.commande.controller;
import com.commande.model.Commande;
import com.commande.service.CommandeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/commandes")
public class CommandeController {
@Autowired
private CommandeService commandeService;
@PostMapping
public ResponseEntity<Commande> createCommande(@RequestBody Map<String, Object> commandeData) {
Long produitId = Long.valueOf(commandeData.get("produitId").toString());
int quantite = Integer.parseInt(commandeData.get("quantite").toString());
Commande commande = commandeService.createCommande(produitId, quantite);
return ResponseEntity.ok(commande);
}
}Endpoint exposé : POST /commandes pour créer une commande avec produitId et quantite.
6. Configuration
Fichier application.properties :
server.port=8081
spring.application.name=commande-service
# H2 Database
spring.datasource.url=jdbc:h2:mem:commandeDB
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
# JPA
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
# Kafka
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializerConfiguration Kafka : Le service se connecte au broker Kafka sur localhost:9092 et utilise des sérialiseurs de type String pour les clés et valeurs.
Classe principale CommandeServiceApplication.java :
package com.commande;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CommandeServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CommandeServiceApplication.class, args);
}
}Étape 4 : Développement du service Notification
1. Création du projet Maven
Création du projet notificationService avec les dépendances Spring Boot Web et Spring Kafka dans pom.xml :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
</dependencies>Ce service ne nécessite pas de base de données car il consomme uniquement des messages Kafka.
2. Listener Kafka
Création de NotificationListener.java dans le package com.notification.listener :
package com.notification.listener;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@Component
public class NotificationListener {
@KafkaListener(topics = "commande-creee", groupId = "notification-service-group")
public void handleCommandeCreee(String message) {
System.out.println("========================================");
System.out.println("Notification reçue !");
System.out.println("Message: " + message);
System.out.println("========================================");
}
}Annotation @KafkaListener : Configure le listener pour écouter le topic commande-creee avec le groupe de consommateurs notification-service-group.
Comportement : Dès qu’un message est publié sur le topic commande-creee, la méthode handleCommandeCreee est invoquée automatiquement et affiche le message dans les logs.
3. Configuration
Fichier application.properties :
server.port=8083
spring.application.name=notification-service
# Kafka
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.group-id=notification-service-groupConfiguration Kafka Consumer : Le service se connecte au broker Kafka et utilise des désérialiseurs String. Le groupe de consommateurs permet de gérer les offsets et d’assurer qu’un message n’est traité qu’une seule fois.
Classe principale NotificationServiceApplication.java :
package com.notification;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class NotificationServiceApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationServiceApplication.class, args);
}
}Étape 5 : Tests et résultats
1. Démarrage des services
Lancement des trois microservices dans l’ordre :
# Terminal 1 - Produit Service
cd produitService
mvn spring-boot:run
# Terminal 2 - Commande Service
cd commandeService
mvn spring-boot:run
# Terminal 3 - Notification Service
cd notificationService
mvn spring-boot:runLes trois services démarrent correctement sur leurs ports respectifs.
2. Test avec Postman
Test 1 : Création d’un produit
Requête POST vers http://localhost:8082/produits :
{
"nom": "Laptop Dell XPS 15",
"prix": 1299.99
}Réponse obtenue :
{
"id": 1,
"nom": "Laptop Dell XPS 15",
"prix": 1299.99
}Le produit est créé avec succès et stocké dans la base H2 du service Produit.
Test 2 : Récupération des produits
Requête GET vers http://localhost:8082/produits :
Réponse :
[
{
"id": 1,
"nom": "Laptop Dell XPS 15",
"prix": 1299.99
}
]La liste des produits est récupérée correctement.
Test 3 : Création d’une commande
Requête POST vers http://localhost:8081/commandes :
{
"produitId": 1,
"quantite": 2
}Réponse obtenue :
{
"id": 1,
"produitId": 1,
"quantite": 2
}La commande est créée et persistée dans la base H2 du service Commande.
3. Vérification des logs du service Notification
Immédiatement après la création de la commande, les logs du notificationService affichent :
========================================
Notification reçue !
Message: Commande ID: 1, Produit ID: 1, Quantité: 2
========================================
La communication asynchrone via Kafka fonctionne correctement. Le message publié par commandeService est consommé instantanément par notificationService.
Test 4 : Création d’une seconde commande
Requête POST vers http://localhost:8081/commandes :
{
"produitId": 1,
"quantite": 5
}Réponse :
{
"id": 2,
"produitId": 1,
"quantite": 5
}Logs du service Notification :
========================================
Notification reçue !
Message: Commande ID: 2, Produit ID: 1, Quantité: 5
========================================
Le système gère correctement plusieurs commandes successives. Chaque commande déclenche bien une notification.
4. Vérification dans la console H2
Accès à la console H2 du service Commande via http://localhost:8081/h2-console :
Connexion avec les paramètres configurés (JDBC URL: jdbc:h2:mem:commandeDB, username: sa, password: password).
Requête SQL :
SELECT * FROM commandes;Résultat :
ID | PRODUIT_ID | QUANTITE
----+------------+---------
1 | 1 | 2
2 | 1 | 5
Les commandes sont bien persistées dans la base H2. La synchronisation entre la persistence et la publication Kafka fonctionne correctement.