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
This commit is contained in:
dahoud
2026-01-21 13:46:16 +00:00
parent 7dd0969799
commit 93c63fd600
78 changed files with 5019 additions and 1113 deletions

View File

@@ -0,0 +1,450 @@
# 🚀 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
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
```
**Documentation**: https://quarkus.io/guides/websockets-next
#### 2. Kafka Reactive Messaging
```xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-messaging-kafka</artifactId>
</dependency>
```
**Documentation**: https://quarkus.io/guides/kafka
#### 3. Reactive Messaging HTTP (Bridge Kafka ↔ WebSocket)
```xml
<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é)
```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<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
```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<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
```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<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
- [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