Erweiterungen
This commit is contained in:
318
MESSAGING_LAYER.md
Normal file
318
MESSAGING_LAYER.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# Message Delivery Layer - Implementierungsdokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Der **Message Delivery Layer** ist eine neue Abstraktionsschicht zwischen der Anwendungslogik und dem MQTT-Transport. Er bietet:
|
||||
|
||||
- ✅ **Zuverlässige Nachrichtenzustellung** mit Bestätigungsmechanismus
|
||||
- ✅ **Automatische Wiederholungsversuche** bei fehlgeschlagenen Zustellungen
|
||||
- ✅ **Protokoll-Abstraktion** für einfachen Austausch von MQTT gegen andere Transporte
|
||||
- ✅ **Vollständiges Audit-Log** aller Nachrichten
|
||||
- ✅ **Queue-basiertes Design** für Skalierbarkeit
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
Application Layer (MessageController, Services)
|
||||
↓
|
||||
MessageDeliveryService (Envelope-Wrapping, Queue-Management)
|
||||
↓
|
||||
MessagingTransport Interface (Protokoll-Abstraktion)
|
||||
↓
|
||||
MqttTransportAdapter (MQTT-spezifische Implementierung)
|
||||
↓
|
||||
MqttV5ClientManager (HiveMQ Client)
|
||||
```
|
||||
|
||||
## Komponenten
|
||||
|
||||
### 1. Datenmodelle (`messaging/model/`)
|
||||
|
||||
#### MessageEnvelope
|
||||
Wrapper für alle Nachrichten mit Metadaten:
|
||||
- `messageId`: Eindeutige UUID
|
||||
- `timestamp`: Erstellungszeitpunkt
|
||||
- `topic`: Ziel-Topic
|
||||
- `payload`: Ursprüngliche Nachricht
|
||||
- `requiresAck`: Bestätigung erforderlich?
|
||||
- `retryCount`: Anzahl Wiederholungsversuche
|
||||
- `expiresAt`: Ablaufzeitpunkt
|
||||
|
||||
#### PendingDelivery
|
||||
Tracking-Objekt für ausstehende Zustellungen:
|
||||
- `messageId`: Referenz zur Envelope
|
||||
- `status`: PENDING, SENT, ACKNOWLEDGED, FAILED, EXPIRED
|
||||
- `retryCount`: Aktuelle Wiederholungsversuche
|
||||
- `nextRetryAt`: Zeitpunkt des nächsten Versuchs
|
||||
- `envelopeData`: Serialisierte Envelope-Daten
|
||||
|
||||
#### AcknowledgmentMessage
|
||||
Bestätigungsnachricht vom Client:
|
||||
- `messageId`: ID der bestätigten Nachricht
|
||||
- `status`: RECEIVED, PROCESSED, FAILED
|
||||
- `clientId`: Absender der Bestätigung
|
||||
- `errorMessage`: Optional bei Fehler
|
||||
|
||||
### 2. Transport Layer (`messaging/transport/`)
|
||||
|
||||
#### MessagingTransport Interface
|
||||
Protokoll-agnostische Schnittstelle:
|
||||
```java
|
||||
CompletableFuture<Void> send(String topic, byte[] payload, TransportOptions options);
|
||||
void subscribe(String topicPattern, MessageHandler handler);
|
||||
boolean isConnected();
|
||||
String getTransportType();
|
||||
```
|
||||
|
||||
#### MqttTransportAdapter
|
||||
MQTT-Implementierung des Transport-Interfaces:
|
||||
- Adaptiert HiveMQ MQTT Client
|
||||
- Unterstützt MQTT-Wildcards (+ und #)
|
||||
- QoS-Mapping (0, 1, 2)
|
||||
|
||||
### 3. Delivery Service (`messaging/delivery/`)
|
||||
|
||||
#### MessageDeliveryService
|
||||
Zentrale Orchestrierung:
|
||||
- `sendMessage()`: Nachricht mit Tracking senden
|
||||
- `handleIncomingMessage()`: Eingehende Envelope verarbeiten
|
||||
- `handleAcknowledgment()`: Bestätigung verarbeiten
|
||||
- `retryPendingDeliveries()`: Wiederholungsversuche durchführen
|
||||
- `cleanupOldDeliveries()`: Alte Einträge aufräumen
|
||||
|
||||
#### AcknowledgmentHandler
|
||||
Routing eingehender Nachrichten zur Anwendungslogik:
|
||||
- Unwrapping der Envelope
|
||||
- Routing basierend auf Topic-Pattern
|
||||
- Delegation an MessageController
|
||||
|
||||
#### RetryScheduler
|
||||
Geplante Tasks:
|
||||
- Retry-Task: Alle 30 Sekunden (konfigurierbar)
|
||||
- Cleanup-Task: Stündlich (konfigurierbar)
|
||||
|
||||
### 4. Repositories
|
||||
|
||||
#### MessageEnvelopeRepository
|
||||
Speicherung aller Envelope-Objekte in MongoDB Collection `message_envelopes`
|
||||
|
||||
#### PendingDeliveryRepository
|
||||
Tracking ausstehender Zustellungen in MongoDB Collection `pending_deliveries`
|
||||
|
||||
## Nachrichtenfluss
|
||||
|
||||
### Outbound (Server → Client)
|
||||
|
||||
1. **Anwendung** ruft `MessageDeliveryService.sendMessage()` auf
|
||||
2. **MessageEnvelope** wird erstellt mit eindeutiger `messageId`
|
||||
3. **PendingDelivery** wird in Datenbank gespeichert (Status: PENDING)
|
||||
4. **Envelope** wird als JSON serialisiert
|
||||
5. **Transport** sendet Nachricht via MQTT
|
||||
6. **Status** wird auf SENT aktualisiert, `nextRetryAt` gesetzt
|
||||
7. **Client** empfängt Nachricht und sendet ACK
|
||||
8. **AcknowledgmentHandler** verarbeitet ACK
|
||||
9. **PendingDelivery** wird auf ACKNOWLEDGED gesetzt
|
||||
|
||||
### Inbound (Client → Server)
|
||||
|
||||
1. **MQTT** empfängt Nachricht auf Topic
|
||||
2. **MqttTransportAdapter** leitet an MessageDeliveryService weiter
|
||||
3. **MessageEnvelope** wird aus JSON deserialisiert
|
||||
4. **ACK** wird automatisch an Client gesendet (falls `requiresAck=true`)
|
||||
5. **AcknowledgmentHandler** routet Payload zur Anwendung
|
||||
6. **MessageController** verarbeitet Business-Logik
|
||||
|
||||
## Retry-Mechanismus
|
||||
|
||||
### Exponential Backoff
|
||||
```
|
||||
Versuch 1: 5 Sekunden
|
||||
Versuch 2: 10 Sekunden (5s × 2.0)
|
||||
Versuch 3: 20 Sekunden (10s × 2.0)
|
||||
Versuch 4: 40 Sekunden (20s × 2.0)
|
||||
Maximum: 5 Minuten
|
||||
```
|
||||
|
||||
### Ablauf
|
||||
1. **RetryScheduler** läuft alle 30 Sekunden
|
||||
2. Findet alle `PendingDelivery` mit Status SENT und `nextRetryAt` in Vergangenheit
|
||||
3. Prüft auf Ablauf (`expiresAt`) und Max-Retries
|
||||
4. Sendet Nachricht erneut via Transport
|
||||
5. Aktualisiert `retryCount` und `nextRetryAt`
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### application.properties
|
||||
```properties
|
||||
# Message Delivery Layer Configuration
|
||||
app.messaging.delivery.max-retries=3
|
||||
app.messaging.delivery.retry-initial-delay=5s
|
||||
app.messaging.delivery.retry-max-delay=5m
|
||||
app.messaging.delivery.retry-backoff-multiplier=2.0
|
||||
app.messaging.delivery.ack-timeout=30s
|
||||
app.messaging.delivery.message-expiry=24h
|
||||
app.messaging.delivery.cleanup-interval-minutes=60
|
||||
app.messaging.delivery.retry-interval-seconds=30
|
||||
app.messaging.delivery.acknowledged-retention-days=7
|
||||
```
|
||||
|
||||
## MQTT Topics
|
||||
|
||||
### Outbound (Server → Client)
|
||||
```
|
||||
/client/{clientId}/message # Chat-Nachrichten (wrapped)
|
||||
/client/{clientId}/jobs # Job-Zuweisungen (wrapped)
|
||||
/client/{clientId}/auth # Login-Antworten (wrapped)
|
||||
```
|
||||
|
||||
### Inbound (Client → Server)
|
||||
```
|
||||
/server/{clientId}/task_completed # Task-Abschlüsse (wrapped)
|
||||
/server/{clientId}/message # Chat-Nachrichten (wrapped)
|
||||
/server/{clientId}/jobs/assigned # Job-Anfragen (wrapped)
|
||||
/server/login # Login-Anfragen (wrapped)
|
||||
```
|
||||
|
||||
### Acknowledgments
|
||||
```
|
||||
/ack/server/{messageId} # Client → Server ACK
|
||||
/ack/client/{clientId}/{messageId} # Server → Client ACK
|
||||
```
|
||||
|
||||
## Migration bestehender Clients
|
||||
|
||||
### Phase 1: Server-seitig (✅ Abgeschlossen)
|
||||
- Message Delivery Layer implementiert
|
||||
- MqttPublisher nutzt jetzt MessageDeliveryService
|
||||
- Backward-kompatibel: Legacy-Nachrichten werden weiterhin verarbeitet
|
||||
|
||||
### Phase 2: Client-seitig (TODO)
|
||||
Flutter-App muss angepasst werden:
|
||||
|
||||
1. **Envelope-Handling**
|
||||
```dart
|
||||
class MessageEnvelope {
|
||||
String messageId;
|
||||
DateTime timestamp;
|
||||
String topic;
|
||||
Map<String, dynamic> payload;
|
||||
bool requiresAck;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Empfang**
|
||||
```dart
|
||||
void handleIncomingMessage(String topic, String json) {
|
||||
final envelope = MessageEnvelope.fromJson(json);
|
||||
|
||||
// ACK senden
|
||||
if (envelope.requiresAck) {
|
||||
sendAcknowledgment(envelope.messageId);
|
||||
}
|
||||
|
||||
// Payload verarbeiten
|
||||
processPayload(envelope.payload);
|
||||
}
|
||||
```
|
||||
|
||||
3. **ACK senden**
|
||||
```dart
|
||||
void sendAcknowledgment(String messageId) {
|
||||
final ack = {
|
||||
'messageId': messageId,
|
||||
'status': 'RECEIVED',
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
'clientId': myClientId
|
||||
};
|
||||
|
||||
mqtt.publish('/ack/server/$messageId', jsonEncode(ack));
|
||||
}
|
||||
```
|
||||
|
||||
4. **Senden**
|
||||
```dart
|
||||
void sendMessage(String topic, Map<String, dynamic> payload) {
|
||||
final envelope = MessageEnvelope(
|
||||
messageId: Uuid().v4(),
|
||||
timestamp: DateTime.now(),
|
||||
topic: topic,
|
||||
payload: payload,
|
||||
requiresAck: true
|
||||
);
|
||||
|
||||
mqtt.publish(topic, jsonEncode(envelope.toJson()));
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring & Debugging
|
||||
|
||||
### Logging
|
||||
Alle Komponenten loggen mit Präfix:
|
||||
- `[MessageDelivery]`: MessageDeliveryService
|
||||
- `[MqttTransport]`: MqttTransportAdapter
|
||||
- `[AckHandler]`: AcknowledgmentHandler
|
||||
- `[RetryScheduler]`: RetryScheduler
|
||||
- `[MessagingConfig]`: MessagingConfig
|
||||
|
||||
### Datenbank-Queries
|
||||
|
||||
**Ausstehende Zustellungen:**
|
||||
```javascript
|
||||
db.pending_deliveries.find({ status: "SENT" })
|
||||
```
|
||||
|
||||
**Fehlgeschlagene Zustellungen:**
|
||||
```javascript
|
||||
db.pending_deliveries.find({ status: "FAILED" })
|
||||
```
|
||||
|
||||
**Retry-Statistiken:**
|
||||
```javascript
|
||||
db.pending_deliveries.aggregate([
|
||||
{ $group: { _id: "$status", count: { $sum: 1 } } }
|
||||
])
|
||||
```
|
||||
|
||||
**Nachrichten eines Clients:**
|
||||
```javascript
|
||||
db.pending_deliveries.find({ client_id: "client123" })
|
||||
```
|
||||
|
||||
## Vorteile
|
||||
|
||||
### Zuverlässigkeit
|
||||
- Garantierte Zustellung durch Retry-Mechanismus
|
||||
- Persistierung in MongoDB verhindert Datenverlust
|
||||
- Acknowledgment-Tracking für Nachvollziehbarkeit
|
||||
|
||||
### Skalierbarkeit
|
||||
- Queue-basiertes Design
|
||||
- Asynchrone Verarbeitung mit CompletableFuture
|
||||
- Scheduled Tasks für Hintergrundverarbeitung
|
||||
|
||||
### Wartbarkeit
|
||||
- Klare Trennung der Verantwortlichkeiten
|
||||
- Protokoll-Abstraktion ermöglicht einfachen Austausch
|
||||
- Umfangreiches Logging für Debugging
|
||||
|
||||
### Flexibilität
|
||||
- Konfigurierbare Retry-Parameter
|
||||
- Verschiedene DeliveryOptions (standard, fireAndForget, critical)
|
||||
- Erweiterbar für andere Transporte (WebSocket, gRPC, etc.)
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
1. ✅ Server-seitige Implementierung abgeschlossen
|
||||
2. ⏳ Flutter-App anpassen (Envelope-Handling)
|
||||
3. ⏳ Integration-Tests schreiben
|
||||
4. ⏳ Monitoring-Dashboard erstellen
|
||||
5. ⏳ Performance-Optimierung
|
||||
6. ⏳ Dokumentation für Client-Entwickler
|
||||
|
||||
## Support
|
||||
|
||||
Bei Fragen oder Problemen:
|
||||
- Logs prüfen: `logs/votianlt.log`
|
||||
- MongoDB Collections: `message_envelopes`, `pending_deliveries`
|
||||
- Konfiguration: `application.properties`
|
||||
|
||||
Reference in New Issue
Block a user