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