Files
mic-after-work-server-impl-…/REALTIME_ARCHITECTURE_BRAINSTORM.md
dahoud 93c63fd600 feat: migration complète vers WebSockets Next + Kafka pour temps réel
- 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
2026-01-21 13:46:16 +00:00

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_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)

<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 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

Kafka

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