Files
votianlt/MESSAGING_LAYER.md
2025-10-23 12:18:42 +02:00

9.0 KiB
Raw Blame History

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:

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

# 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

    class MessageEnvelope {
      String messageId;
      DateTime timestamp;
      String topic;
      Map<String, dynamic> payload;
      bool requiresAck;
    }
    
  2. 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);
    }
    
  3. 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));
    }
    
  4. 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

  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