9.0 KiB
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 UUIDtimestamp: Erstellungszeitpunkttopic: Ziel-Topicpayload: Ursprüngliche NachrichtrequiresAck: Bestätigung erforderlich?retryCount: Anzahl WiederholungsversucheexpiresAt: Ablaufzeitpunkt
PendingDelivery
Tracking-Objekt für ausstehende Zustellungen:
messageId: Referenz zur Envelopestatus: PENDING, SENT, ACKNOWLEDGED, FAILED, EXPIREDretryCount: Aktuelle WiederholungsversuchenextRetryAt: Zeitpunkt des nächsten VersuchsenvelopeData: Serialisierte Envelope-Daten
AcknowledgmentMessage
Bestätigungsnachricht vom Client:
messageId: ID der bestätigten Nachrichtstatus: RECEIVED, PROCESSED, FAILEDclientId: Absender der BestätigungerrorMessage: Optional bei Fehler
2. Transport Layer (messaging/transport/)
MessagingTransport Interface
Protokoll-agnostische Schnittstelle:
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 sendenhandleIncomingMessage(): Eingehende Envelope verarbeitenhandleAcknowledgment(): Bestätigung verarbeitenretryPendingDeliveries(): Wiederholungsversuche durchführencleanupOldDeliveries(): 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)
- Anwendung ruft
MessageDeliveryService.sendMessage()auf - MessageEnvelope wird erstellt mit eindeutiger
messageId - PendingDelivery wird in Datenbank gespeichert (Status: PENDING)
- Envelope wird als JSON serialisiert
- Transport sendet Nachricht via MQTT
- Status wird auf SENT aktualisiert,
nextRetryAtgesetzt - Client empfängt Nachricht und sendet ACK
- AcknowledgmentHandler verarbeitet ACK
- PendingDelivery wird auf ACKNOWLEDGED gesetzt
Inbound (Client → Server)
- MQTT empfängt Nachricht auf Topic
- MqttTransportAdapter leitet an MessageDeliveryService weiter
- MessageEnvelope wird aus JSON deserialisiert
- ACK wird automatisch an Client gesendet (falls
requiresAck=true) - AcknowledgmentHandler routet Payload zur Anwendung
- 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
- RetryScheduler läuft alle 30 Sekunden
- Findet alle
PendingDeliverymit Status SENT undnextRetryAtin Vergangenheit - Prüft auf Ablauf (
expiresAt) und Max-Retries - Sendet Nachricht erneut via Transport
- Aktualisiert
retryCountundnextRetryAt
Konfiguration
application.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:
-
Envelope-Handling
class MessageEnvelope { String messageId; DateTime timestamp; String topic; Map<String, dynamic> payload; bool requiresAck; } -
Empfang
void handleIncomingMessage(String topic, String json) { final envelope = MessageEnvelope.fromJson(json); // ACK senden if (envelope.requiresAck) { sendAcknowledgment(envelope.messageId); } // Payload verarbeiten processPayload(envelope.payload); } -
ACK senden
void sendAcknowledgment(String messageId) { final ack = { 'messageId': messageId, 'status': 'RECEIVED', 'timestamp': DateTime.now().toIso8601String(), 'clientId': myClientId }; mqtt.publish('/ack/server/$messageId', jsonEncode(ack)); } -
Senden
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:
db.pending_deliveries.find({ status: "SENT" })
Fehlgeschlagene Zustellungen:
db.pending_deliveries.find({ status: "FAILED" })
Retry-Statistiken:
db.pending_deliveries.aggregate([
{ $group: { _id: "$status", count: { $sum: 1 } } }
])
Nachrichten eines Clients:
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
- ✅ Server-seitige Implementierung abgeschlossen
- ⏳ Flutter-App anpassen (Envelope-Handling)
- ⏳ Integration-Tests schreiben
- ⏳ Monitoring-Dashboard erstellen
- ⏳ Performance-Optimierung
- ⏳ 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