319 lines
9.0 KiB
Markdown
319 lines
9.0 KiB
Markdown
# 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`
|
||
|