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:
450
REALTIME_ARCHITECTURE_BRAINSTORM.md
Normal file
450
REALTIME_ARCHITECTURE_BRAINSTORM.md
Normal 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
|
||||
Reference in New Issue
Block a user