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

451 lines
16 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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