# 🚀 Architecture Temps Réel - Brainstorming & Plan d'Implémentation ## 📋 Contexte Actuel ### État des Lieux - ✅ **Backend**: Jakarta WebSocket (`@ServerEndpoint`) - API legacy - ✅ **Frontend**: `web_socket_channel` pour WebSocket - ✅ **Services existants**: - `NotificationWebSocket` (`/notifications/ws/{userId}`) - `ChatWebSocket` (`/chat/ws/{userId}`) - Services Flutter: `RealtimeNotificationService`, `ChatWebSocketService` ### Limitations Actuelles 1. **Pas de persistance des événements** : Si un utilisateur est déconnecté, les messages sont perdus 2. **Pas de scalabilité horizontale** : Les sessions WebSocket sont en mémoire, ne fonctionnent pas avec plusieurs instances 3. **Pas de garantie de livraison** : Pas de mécanisme de retry ou de queue 4. **Pas de découplage** : Services directement couplés aux WebSockets --- ## 🎯 Objectifs 1. **Garantir la livraison** : Aucun événement ne doit être perdu 2. **Scalabilité horizontale** : Support de plusieurs instances Quarkus 3. **Temps réel garanti** : Latence < 100ms pour les notifications critiques 4. **Durabilité** : Persistance des événements pour récupération après déconnexion 5. **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) ```xml io.quarkus quarkus-websockets-next ``` **Documentation**: https://quarkus.io/guides/websockets-next #### 2. Kafka Reactive Messaging ```xml io.quarkus quarkus-messaging-kafka ``` **Documentation**: https://quarkus.io/guides/kafka #### 3. Reactive Messaging HTTP (Bridge Kafka ↔ WebSocket) ```xml io.quarkiverse.reactivemessaginghttp quarkus-reactive-messaging-http 1.0.0 ``` **Documentation**: https://docs.quarkiverse.io/quarkus-reactive-messaging-http/dev/reactive-messaging-websocket.html ### Frontend (Flutter) #### Package WebSocket (Déjà utilisé) ```yaml dependencies: web_socket_channel: ^2.4.0 ``` **Documentation**: https://pub.dev/packages/web_socket_channel --- ## 🔧 Configuration ### application.properties (Quarkus) ```properties # ============================================ # 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) ```java 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 notificationEmitter; // Stockage des connexions actives (pour routing) private static final Map 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 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 ```java 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 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 ```dart // 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 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 `NotificationWebSocket` vers WebSockets Next - Migrer `ChatWebSocket` vers 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 - [WebSockets Next Guide](https://quarkus.io/guides/websockets-next) - [Kafka Guide](https://quarkus.io/guides/kafka) - [Reactive Messaging](https://quarkus.io/guides/messaging) ### Kafka - [Kafka Documentation](https://kafka.apache.org/documentation/) - [Quarkus Kafka Dev Services](https://quarkus.io/guides/kafka-dev-services) ### Flutter - [web_socket_channel Package](https://pub.dev/packages/web_socket_channel) - [Flutter WebSocket Best Practices](https://ably.com/topic/websockets-flutter) --- ## ✅ Recommandation Finale **Architecture recommandée**: **WebSockets Next + Kafka** **Raisons**: 1. ✅ Scalabilité horizontale garantie 2. ✅ Durabilité des événements 3. ✅ Découplage des services 4. ✅ Compatible avec l'existant (migration progressive) 5. ✅ Support natif Quarkus (Dev Services pour Kafka) 6. ✅ Monitoring intégré **Prochaines étapes**: 1. Ajouter les dépendances dans `pom.xml` 2. Créer un POC avec un seul endpoint (notifications) 3. Tester la scalabilité avec 2 instances Quarkus 4. Migrer progressivement les autres endpoints