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

399 lines
10 KiB
Markdown

# MQTT Migration Guide - Client Implementation
## Übersicht
Dieses Dokument beschreibt die Änderungen am MQTT-Messaging-System und wie mobile Clients (Flutter/Dart) auf den neuen Messaging-Layer umgestellt werden müssen.
## Änderungen am Server
### 1. Neue MQTT-Broker-Konfiguration
**Wichtig: Der MQTT-Broker-Port hat sich geändert!**
```
Alter Port: 1883
Neuer Port: 42099
Broker: mqtt-2.assecutor.de:42099
```
### 2. Server-Konfiguration
Die Server-Konfiguration wurde von der alten `app.mqtt.*` Konfiguration auf das neue Plugin-System umgestellt:
**Alte Konfiguration (nicht mehr verwendet):**
```properties
app.mqtt.enabled=true
app.mqtt.broker-uri=mqtt://mqtt-2.assecutor.de
app.mqtt.client-id=server-${random.uuid}
```
**Neue Konfiguration:**
```properties
# Messaging Plugin Configuration
app.messaging.plugin.type=mqtt
app.messaging.plugin.mqtt.broker.host=mqtt-2.assecutor.de
app.messaging.plugin.mqtt.broker.port=42099
app.messaging.plugin.mqtt.username=app
app.messaging.plugin.mqtt.password=apppwd
app.messaging.plugin.mqtt.client.id=votianlt-server
```
### 3. Verbesserte Fehlerbehandlung
Der Server hat jetzt:
- Erhöhten Connection-Timeout (60 Sekunden statt 30)
- Detaillierte Fehlerdiagnose für Verbindungsprobleme
- Automatische Wiederverbindung mit exponentieller Backoff-Strategie
## Client-Anpassungen erforderlich
### 1. MQTT-Broker-Verbindung aktualisieren
**Flutter/Dart Beispiel:**
```dart
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
class MqttService {
// WICHTIG: Neuer Port!
static const String BROKER_HOST = 'mqtt-2.assecutor.de';
static const int BROKER_PORT = 42099; // Geändert von 1883
static const String USERNAME = 'app';
static const String PASSWORD = 'apppwd';
late MqttServerClient client;
Future<void> connect(String clientId) async {
client = MqttServerClient.withPort(
BROKER_HOST,
clientId,
BROKER_PORT, // Neuer Port
);
client.logging(on: true);
client.keepAlivePeriod = 60;
client.connectTimeoutPeriod = 60000; // 60 Sekunden
client.autoReconnect = true;
// Authentifizierung
client.setProtocolV311();
final connMessage = MqttConnectMessage()
.withClientIdentifier(clientId)
.authenticateAs(USERNAME, PASSWORD)
.withWillQos(MqttQos.atLeastOnce)
.startClean()
.keepAliveFor(60);
client.connectionMessage = connMessage;
try {
await client.connect();
print('MQTT Connected successfully');
} catch (e) {
print('MQTT Connection failed: $e');
client.disconnect();
rethrow;
}
}
}
```
### 2. Topic-Struktur (unverändert)
Die Topic-Struktur bleibt gleich:
**Client → Server:**
```
/server/{clientId}/{messageType}
```
**Server → Client:**
```
/client/{clientId}/{messageType}
```
**Acknowledgments:**
```
Client → Server: /ack/server/{messageId}
Server → Client: /ack/client/{clientId}/{messageId}
```
### 3. Message Envelope Format
Der Server verwendet jetzt ein Message-Envelope-Format für alle Nachrichten:
```json
{
"messageId": "uuid-v4",
"timestamp": "2025-10-22T10:30:00Z",
"topic": "/client/app-user-123/jobs/assigned",
"payload": {
// Ihre eigentliche Nachricht
},
"requiresAck": true,
"retryCount": 0,
"expiresAt": "2025-10-23T10:30:00Z"
}
```
**Client-Implementierung:**
```dart
class MessageEnvelope {
final String messageId;
final DateTime timestamp;
final String topic;
final Map<String, dynamic> payload;
final bool requiresAck;
final int retryCount;
final DateTime? expiresAt;
MessageEnvelope({
required this.messageId,
required this.timestamp,
required this.topic,
required this.payload,
this.requiresAck = true,
this.retryCount = 0,
this.expiresAt,
});
factory MessageEnvelope.fromJson(Map<String, dynamic> json) {
return MessageEnvelope(
messageId: json['messageId'],
timestamp: DateTime.parse(json['timestamp']),
topic: json['topic'],
payload: json['payload'],
requiresAck: json['requiresAck'] ?? true,
retryCount: json['retryCount'] ?? 0,
expiresAt: json['expiresAt'] != null
? DateTime.parse(json['expiresAt'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
'messageId': messageId,
'timestamp': timestamp.toIso8601String(),
'topic': topic,
'payload': payload,
'requiresAck': requiresAck,
'retryCount': retryCount,
'expiresAt': expiresAt?.toIso8601String(),
};
}
}
```
### 4. Acknowledgment-Handling
Wenn eine Nachricht mit `requiresAck: true` empfangen wird, muss der Client eine Bestätigung senden:
```dart
class AcknowledgmentMessage {
final String messageId;
final DateTime timestamp;
final String status; // "SUCCESS" oder "FAILED"
final String? errorMessage;
AcknowledgmentMessage({
required this.messageId,
required this.timestamp,
required this.status,
this.errorMessage,
});
Map<String, dynamic> toJson() {
return {
'messageId': messageId,
'timestamp': timestamp.toIso8601String(),
'status': status,
if (errorMessage != null) 'errorMessage': errorMessage,
};
}
}
// Verwendung:
void sendAcknowledgment(String messageId, bool success, [String? error]) {
final ack = AcknowledgmentMessage(
messageId: messageId,
timestamp: DateTime.now(),
status: success ? 'SUCCESS' : 'FAILED',
errorMessage: error,
);
final topic = '/ack/server/$messageId';
final payload = jsonEncode(ack.toJson());
final builder = MqttClientPayloadBuilder();
builder.addString(payload);
client.publishMessage(
topic,
MqttQos.exactlyOnce,
builder.payload!,
);
}
```
### 5. Nachrichten empfangen und verarbeiten
```dart
void setupMessageHandlers() {
client.updates!.listen((List<MqttReceivedMessage<MqttMessage>> messages) {
for (var message in messages) {
final topic = message.topic;
final payload = MqttPublishPayload.bytesToStringAsString(
(message.payload as MqttPublishMessage).payload.message,
);
try {
final json = jsonDecode(payload);
// Prüfen, ob es ein Envelope ist
if (json.containsKey('messageId') && json.containsKey('payload')) {
final envelope = MessageEnvelope.fromJson(json);
// Nachricht verarbeiten
handleMessage(envelope);
// ACK senden, wenn erforderlich
if (envelope.requiresAck) {
sendAcknowledgment(envelope.messageId, true);
}
} else {
// Legacy-Nachricht ohne Envelope
handleLegacyMessage(topic, json);
}
} catch (e) {
print('Error processing message: $e');
// Bei Envelope-Nachrichten: Fehler-ACK senden
if (json.containsKey('messageId')) {
sendAcknowledgment(json['messageId'], false, e.toString());
}
}
}
});
}
```
### 6. Nachrichten senden
```dart
Future<void> sendMessage(
String messageType,
Map<String, dynamic> payload,
{bool requiresAck = true}
) async {
final envelope = MessageEnvelope(
messageId: Uuid().v4(),
timestamp: DateTime.now(),
topic: '/server/$clientId/$messageType',
payload: payload,
requiresAck: requiresAck,
);
final topic = envelope.topic;
final message = jsonEncode(envelope.toJson());
final builder = MqttClientPayloadBuilder();
builder.addString(message);
client.publishMessage(
topic,
MqttQos.exactlyOnce,
builder.payload!,
);
print('Message sent: $messageType');
}
```
## Migration Checklist für Clients
- [ ] **MQTT-Broker-Port auf 42099 ändern**
- [ ] **Connection-Timeout auf mindestens 60 Sekunden erhöhen**
- [ ] **Keep-Alive auf 60 Sekunden setzen**
- [ ] **Authentifizierung hinzufügen** (username: `app`, password: `apppwd`)
- [ ] **MessageEnvelope-Klasse implementieren**
- [ ] **AcknowledgmentMessage-Klasse implementieren**
- [ ] **Envelope-basierte Nachrichtenverarbeitung implementieren**
- [ ] **ACK-Handling für eingehende Nachrichten implementieren**
- [ ] **Ausgehende Nachrichten in Envelopes verpacken**
- [ ] **Fehlerbehandlung für abgelaufene Nachrichten implementieren**
- [ ] **Retry-Logik für fehlgeschlagene Nachrichten implementieren**
## Abwärtskompatibilität
Der Server unterstützt derzeit noch Legacy-Nachrichten ohne Envelope-Format, aber es wird empfohlen, so schnell wie möglich auf das neue Format umzustellen.
**Legacy-Format (wird noch unterstützt):**
```json
{
"taskId": "123",
"status": "completed"
}
```
**Neues Format (empfohlen):**
```json
{
"messageId": "uuid",
"timestamp": "2025-10-22T10:30:00Z",
"topic": "/server/client-123/task_completed",
"payload": {
"taskId": "123",
"status": "completed"
},
"requiresAck": true
}
```
## Vorteile des neuen Systems
1. **Zuverlässige Zustellung**: ACK-basiertes System mit automatischen Wiederholungen
2. **Nachrichtenverfolgung**: Jede Nachricht hat eine eindeutige ID
3. **Ablaufverwaltung**: Nachrichten können ablaufen und werden automatisch bereinigt
4. **Bessere Fehlerbehandlung**: Detaillierte Fehlerinformationen
5. **Monitoring**: Vollständige Nachrichtenverfolgung im System
## Testen der Verbindung
```dart
Future<void> testConnection() async {
try {
final mqttService = MqttService();
await mqttService.connect('test-client-${Uuid().v4()}');
// Test-Nachricht senden
await mqttService.sendMessage(
'test',
{'message': 'Hello from client'},
);
print('Connection test successful!');
} catch (e) {
print('Connection test failed: $e');
}
}
```
## Support und Fragen
Bei Fragen oder Problemen während der Migration:
1. Prüfen Sie die Logs auf beiden Seiten (Client und Server)
2. Stellen Sie sicher, dass der Port 42099 erreichbar ist
3. Überprüfen Sie die Authentifizierungsdaten
4. Testen Sie die Verbindung mit einem MQTT-Client-Tool (z.B. MQTT Explorer)
## Weitere Dokumentation
- `MESSAGING_LAYER.md` - Detaillierte Architektur des Messaging-Systems
- `MQTT_README.md` - MQTT-API-Dokumentation
- `CLAUDE.md` - Allgemeine Systemarchitektur