- Migration de Jakarta WebSocket vers Quarkus WebSockets Next - Implémentation de l'architecture Kafka pour événements temps réel - Ajout des DTOs d'événements (NotificationEvent, ChatMessageEvent, ReactionEvent, PresenceEvent) - Création des bridges Kafka → WebSocket (NotificationKafkaBridge, ChatKafkaBridge, ReactionKafkaBridge) - Mise à jour des services pour publier dans Kafka au lieu d'appeler directement WebSocket - Suppression des classes obsolètes (ChatWebSocket, NotificationWebSocket) - Correction de l'injection des paramètres path dans WebSockets Next (utilisation de connection.pathParam) - Ajout des migrations DB pour bookings, promotions, business hours, amenities, reviews - Mise à jour de la configuration application.properties pour Kafka et WebSockets Next - Mise à jour .gitignore pour ignorer les fichiers de logs
16 KiB
16 KiB
🚀 Architecture Temps Réel - Brainstorming & Plan d'Implémentation
📋 Contexte Actuel
État des Lieux
- ✅ Backend: Jakarta WebSocket (
@ServerEndpoint) - API legacy - ✅ Frontend:
web_socket_channelpour WebSocket - ✅ Services existants:
NotificationWebSocket(/notifications/ws/{userId})ChatWebSocket(/chat/ws/{userId})- Services Flutter:
RealtimeNotificationService,ChatWebSocketService
Limitations Actuelles
- Pas de persistance des événements : Si un utilisateur est déconnecté, les messages sont perdus
- Pas de scalabilité horizontale : Les sessions WebSocket sont en mémoire, ne fonctionnent pas avec plusieurs instances
- Pas de garantie de livraison : Pas de mécanisme de retry ou de queue
- Pas de découplage : Services directement couplés aux WebSockets
🎯 Objectifs
- Garantir la livraison : Aucun événement ne doit être perdu
- Scalabilité horizontale : Support de plusieurs instances Quarkus
- Temps réel garanti : Latence < 100ms pour les notifications critiques
- Durabilité : Persistance des événements pour récupération après déconnexion
- Découplage : Services métier indépendants des WebSockets
🏗️ Architecture Proposée
Option 1 : WebSockets Next + Kafka (Recommandée) ⭐
┌─────────────┐
│ Flutter │
│ Client │
└──────┬──────┘
│ WebSocket (wss://)
│
┌──────▼─────────────────────────────────────┐
│ Quarkus WebSockets Next │
│ ┌──────────────────────────────────────┐ │
│ │ @WebSocket("/notifications/{userId}")│ │
│ │ @WebSocket("/chat/{userId}") │ │
│ └──────┬───────────────────────────────┘ │
│ │ │
│ ┌──────▼──────────────────────────────┐ │
│ │ Reactive Messaging Bridge │ │
│ │ @Incoming("kafka-notifications") │ │
│ │ @Outgoing("websocket-notifications") │ │
│ └──────┬───────────────────────────────┘ │
└─────────┼──────────────────────────────────┘
│
│ Kafka Topics
│
┌─────────▼──────────────────────────────────┐
│ Apache Kafka Cluster │
│ ┌──────────────────────────────────────┐ │
│ │ Topics: │ │
│ │ - notifications.{userId} │ │
│ │ - chat.messages │ │
│ │ - reactions.{postId} │ │
│ │ - presence.updates │ │
│ └──────────────────────────────────────┘ │
└─────────┬──────────────────────────────────┘
│
│ Producers
│
┌─────────▼──────────────────────────────────┐
│ Services Métier (Quarkus) │
│ - FriendshipService │
│ - MessageService │
│ - SocialPostService │
│ - EventService │
└────────────────────────────────────────────┘
Avantages
- ✅ Scalabilité : Kafka gère la distribution entre instances
- ✅ Durabilité : Messages persistés dans Kafka (rétention configurable)
- ✅ Découplage : Services publient dans Kafka, WebSocket consomme
- ✅ Performance : WebSockets Next est plus performant que Jakarta WS
- ✅ Replay : Possibilité de rejouer les événements pour récupération
- ✅ Monitoring : Kafka fournit des métriques natives
Inconvénients
- ⚠️ Complexité : Nécessite un cluster Kafka (mais Quarkus Dev Services le gère automatiquement)
- ⚠️ Latence : Légèrement plus élevée (Kafka + WebSocket vs WebSocket direct)
- ⚠️ Ressources : Kafka consomme plus de mémoire
📦 Technologies & Dépendances
Backend (Quarkus)
1. WebSockets Next (Remplace Jakarta WebSocket)
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
Documentation: https://quarkus.io/guides/websockets-next
2. Kafka Reactive Messaging
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-messaging-kafka</artifactId>
</dependency>
Documentation: https://quarkus.io/guides/kafka
3. Reactive Messaging HTTP (Bridge Kafka ↔ WebSocket)
<dependency>
<groupId>io.quarkiverse.reactivemessaginghttp</groupId>
<artifactId>quarkus-reactive-messaging-http</artifactId>
<version>1.0.0</version>
</dependency>
Documentation: https://docs.quarkiverse.io/quarkus-reactive-messaging-http/dev/reactive-messaging-websocket.html
Frontend (Flutter)
Package WebSocket (Déjà utilisé)
dependencies:
web_socket_channel: ^2.4.0
Documentation: https://pub.dev/packages/web_socket_channel
🔧 Configuration
application.properties (Quarkus)
# ============================================
# Kafka Configuration
# ============================================
kafka.bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
# Topics
mp.messaging.outgoing.notifications.connector=smallrye-kafka
mp.messaging.outgoing.notifications.topic=notifications
mp.messaging.outgoing.notifications.key.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.notifications.value.serializer=io.quarkus.kafka.client.serialization.JsonbSerializer
mp.messaging.outgoing.chat-messages.connector=smallrye-kafka
mp.messaging.outgoing.chat-messages.topic=chat.messages
mp.messaging.outgoing.chat-messages.key.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.chat-messages.value.serializer=io.quarkus.kafka.client.serialization.JsonbSerializer
mp.messaging.outgoing.reactions.connector=smallrye-kafka
mp.messaging.outgoing.reactions.topic=reactions
mp.messaging.outgoing.reactions.key.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.reactions.value.serializer=io.quarkus.kafka.client.serialization.JsonbSerializer
# ============================================
# WebSocket Configuration
# ============================================
# WebSockets Next
quarkus.websockets-next.server.enabled=true
quarkus.websockets-next.server.port=8080
# ============================================
# Reactive Messaging HTTP (Bridge)
# ============================================
# Incoming Kafka → Outgoing WebSocket
mp.messaging.incoming.kafka-notifications.connector=smallrye-kafka
mp.messaging.incoming.kafka-notifications.topic=notifications
mp.messaging.incoming.kafka-notifications.group.id=websocket-bridge
mp.messaging.incoming.kafka-notifications.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.kafka-notifications.value.deserializer=io.quarkus.kafka.client.serialization.JsonbDeserializer
mp.messaging.outgoing.ws-notifications.connector=quarkus-websocket
mp.messaging.outgoing.ws-notifications.path=/notifications/{userId}
💻 Implémentation
Backend : Migration vers WebSockets Next
1. Nouveau NotificationWebSocket (WebSockets Next)
package com.lions.dev.websocket;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnClose;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Multi;
import jakarta.inject.Inject;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import org.eclipse.microprofile.reactive.messaging.Incoming;
import org.eclipse.microprofile.reactive.messaging.Outgoing;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
/**
* WebSocket endpoint pour les notifications en temps réel (WebSockets Next).
*
* Architecture:
* - Services métier → Kafka Topic → WebSocket Bridge → Client
*/
@WebSocket(path = "/notifications/{userId}")
public class NotificationWebSocketNext {
@Inject
@Channel("ws-notifications")
Emitter<String> notificationEmitter;
// Stockage des connexions actives (pour routing)
private static final Map<UUID, WebSocketConnection> connections = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(WebSocketConnection connection, String userId) {
UUID userUUID = UUID.fromString(userId);
connections.put(userUUID, connection);
// Envoyer confirmation de connexion
connection.sendText("{\"type\":\"connected\",\"timestamp\":" + System.currentTimeMillis() + "}");
}
@OnClose
public void onClose(String userId) {
UUID userUUID = UUID.fromString(userId);
connections.remove(userUUID);
}
@OnTextMessage
public void onMessage(String message, String userId) {
// Gérer les messages du client (ping, ack, etc.)
// ...
}
/**
* Bridge: Consomme depuis Kafka et envoie via WebSocket
*/
@Incoming("kafka-notifications")
@Outgoing("ws-notifications")
public Multi<String> bridgeNotifications(String kafkaMessage) {
// Parser le message Kafka
// Extraire userId depuis la clé Kafka
// Router vers la bonne connexion WebSocket
return Multi.createFrom().item(kafkaMessage);
}
}
2. Service Métier Publie dans Kafka
package com.lions.dev.service;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import jakarta.inject.Inject;
@ApplicationScoped
public class FriendshipService {
@Inject
@Channel("notifications")
Emitter<NotificationEvent> notificationEmitter;
public void sendFriendRequest(UUID fromUserId, UUID toUserId) {
// ... logique métier ...
// Publier dans Kafka au lieu d'appeler directement WebSocket
NotificationEvent event = new NotificationEvent(
toUserId.toString(),
"friend_request",
Map.of("fromUserId", fromUserId.toString(), "fromName", fromUser.getFirstName())
);
notificationEmitter.send(event);
}
}
Frontend : Amélioration du Service WebSocket
// afterwork/lib/data/services/realtime_notification_service_v2.dart
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status;
class RealtimeNotificationServiceV2 {
WebSocketChannel? _channel;
Timer? _heartbeatTimer;
Timer? _reconnectTimer;
int _reconnectAttempts = 0;
static const int _maxReconnectAttempts = 5;
static const Duration _heartbeatInterval = Duration(seconds: 30);
static const Duration _reconnectDelay = Duration(seconds: 5);
Future<void> connect(String userId, String authToken) async {
final uri = Uri.parse('wss://api.afterwork.lions.dev/notifications/$userId');
_channel = WebSocketChannel.connect(
uri,
protocols: ['notifications-v2'],
headers: {
'Authorization': 'Bearer $authToken',
},
);
// Heartbeat pour maintenir la connexion
_heartbeatTimer = Timer.periodic(_heartbeatInterval, (_) {
_channel?.sink.add(jsonEncode({'type': 'ping'}));
});
// Écouter les messages
_channel!.stream.listen(
_handleMessage,
onError: _handleError,
onDone: _handleDisconnection,
cancelOnError: false,
);
}
void _handleDisconnection() {
_heartbeatTimer?.cancel();
_scheduleReconnect();
}
void _scheduleReconnect() {
if (_reconnectAttempts < _maxReconnectAttempts) {
_reconnectTimer = Timer(_reconnectDelay * (_reconnectAttempts + 1), () {
_reconnectAttempts++;
connect(_userId, _authToken);
});
}
}
}
📊 Comparaison des Options
| Critère | Jakarta WS (Actuel) | WebSockets Next | WebSockets Next + Kafka |
|---|---|---|---|
| Performance | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Scalabilité | ❌ (1 instance) | ⚠️ (limité) | ✅ (illimitée) |
| Durabilité | ❌ | ❌ | ✅ |
| Découplage | ❌ | ⚠️ | ✅ |
| Complexité | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Latence | < 50ms | < 50ms | < 100ms |
| Replay Events | ❌ | ❌ | ✅ |
| Monitoring | ⚠️ | ⚠️ | ✅ (Kafka metrics) |
🎯 Plan d'Implémentation Recommandé
Phase 1 : Migration WebSockets Next (Sans Kafka)
Durée: 1-2 semaines
- Migrer
NotificationWebSocketvers WebSockets Next - Migrer
ChatWebSocketvers WebSockets Next - Tester avec le frontend existant
- Avantage: Amélioration immédiate des performances
Phase 2 : Intégration Kafka
Durée: 2-3 semaines
- Ajouter dépendances Kafka
- Créer les topics Kafka
- Implémenter les bridges Kafka ↔ WebSocket
- Migrer les services pour publier dans Kafka
- Avantage: Scalabilité et durabilité
Phase 3 : Optimisations
Durée: 1 semaine
- Compression des messages
- Batching pour les notifications
- Monitoring et alertes
- Avantage: Performance et observabilité
🔍 Alternatives Considérées
Option 2 : Server-Sent Events (SSE)
- ✅ Plus simple que WebSocket
- ❌ Unidirectionnel (serveur → client uniquement)
- ❌ Pas adapté pour le chat bidirectionnel
Option 3 : gRPC Streaming
- ✅ Performant
- ❌ Plus complexe à configurer
- ❌ Nécessite HTTP/2
Option 4 : Socket.IO
- ✅ Reconnexion automatique
- ❌ Nécessite Node.js (pas compatible Quarkus)
📚 Ressources & Documentation
Quarkus
Kafka
Flutter
✅ Recommandation Finale
Architecture recommandée: WebSockets Next + Kafka
Raisons:
- ✅ Scalabilité horizontale garantie
- ✅ Durabilité des événements
- ✅ Découplage des services
- ✅ Compatible avec l'existant (migration progressive)
- ✅ Support natif Quarkus (Dev Services pour Kafka)
- ✅ Monitoring intégré
Prochaines étapes:
- Ajouter les dépendances dans
pom.xml - Créer un POC avec un seul endpoint (notifications)
- Tester la scalabilité avec 2 instances Quarkus
- Migrer progressivement les autres endpoints