- 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
451 lines
16 KiB
Markdown
451 lines
16 KiB
Markdown
# 🚀 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
|