3. Antwortformate und Fehlerbehandlung
Alle Aufrufe der V5 API geben entweder ein Typ-DTO oder einen boolean/long-Wert zurück. Im Falle eines Fehlers werden folgende sichere Standardwerte zurückgegeben.
3.1 V5 Antwortmuster
| Methodenmuster | Rückgabe bei Erfolg | Rückgabe bei Fehler |
|---|---|---|
create(req) / update(id, req) / get(id) | *ResponseV5 Instanz | null |
list()-Serie | List<*ResponseV5> | Leere Liste (niemals null) |
delete(id) | true | false |
exists(id) | true (vorhanden) | false (nicht vorhanden oder Fehler) |
count()-Serie | long (≥0) | 0 |
Order-Lebenszyklus (start/end/pause/resume/abort) | true (Erfolg) | false (z. B. Übergang nicht möglich) |
Ausnahmen werden nicht geworfen. HTTP-Fehler, Server-ERROR und Fehler beim Parsen von JSON werden alle in den oben genannten sicheren Standardwerten aufgefangen. Die Fehlerursache kann in den Debug-Logs oder im Envelope unter
_code/_messageeingesehen werden.
3.2 Internes Wire-Format (Referenz)
Der V5-Server antwortet in Form eines Envelopes. Da die Client-Bibliothek dies automatisch parst, müssen sich Anwender im normalen Betrieb nicht darum kümmern.
// 단건 성공
{ "_status": "OK", "data": { ... } }
// 목록 성공
{ "_status": "OK", "data": [ { ... }, { ... } ] }
// 목록 — items 키 안에 배열
{ "_status": "OK", "data": { "items": [ ... ] } }
// 에러
{
"_status": "ERROR",
"_code": "E1002",
"_message": "validation failed: ...",
"_http_status": 400
}
3.3 V5 ErrorCode-Mapping
Wenn der Server eine ERROR-Antwort sendet, wird im Feld _code einer der folgenden Codes übertragen. Die maßgebliche Definition ist die plantpulse.core.exception.ErrorCode des Produkts, wobei der HTTP-Status zusammen mit diesem Enum festgelegt wird. Da Code und Status ein Paar bilden, kann die Verzweigung basierend auf einem der beiden erfolgen.
| Code | HTTP | Name | Typische Ursache |
|---|---|---|---|
E1000 | 400 | INVALID_REQUEST | Syntaxfehler, Typenkonflikt |
E1001 | 400 | MISSING_PARAMETER | Fehlender Pflichtparameter |
E1002 | 422 | VALIDATION_FAILED | Verstoß gegen Wertebereich-, Format- oder Domain-Regeln (z. B. ID-Präfix) |
E1100 | 401 | UNAUTHORIZED | api_key fehlt oder ungültig |
E1101 | 429 | AUTH_TOO_MANY_ATTEMPTS | Akkumulierte Authentifizierungsfehler von derselben IP — siehe Retry-After |
E1102 | 401 | TOKEN_EXPIRED | Token abgelaufen — Wiederherstellung durch Neuausstellung möglich |
E1103 | 401 | TOKEN_REVOKED | Token entzogen — derselbe Token kann nicht wiederhergestellt werden |
E1104 | 401 | TOKEN_IP_MISMATCH | Aufruf-IP weicht von der an den Token gebundenen IP ab |
E1200 | 403 | FORBIDDEN | Authentifiziert, aber keine Berechtigung |
E1300 | 404 | NOT_FOUND | Entsprechende ID nicht vorhanden |
E1400 | 405 | METHOD_NOT_ALLOWED | Nicht zulässige Methode |
E1500 | 409 | CONFLICT | Ressourcenkonflikt |
E1501 | 409 | DUPLICATE_ENTITY | Entität existiert bereits |
E1600 | 413 | PAYLOAD_TOO_LARGE | Request-Body zu groß |
E1700 | 415 | UNSUPPORTED_MEDIA_TYPE | Nicht unterstützter Medientyp |
E1800 | 422 | IDEMPOTENCY_CONFLICT | Gleiche(r) Idempotency-Key + anderer Body |
E1801 | 409 | IDEMPOTENCY_IN_PROGRESS | Gleicher Schlüssel wird gerade verarbeitet — bitte später erneut versuchen |
E1900 | 429 | RATE_LIMIT_EXCEEDED | Rate-Limit überschritten |
E5000 | 500 | INTERNAL_ERROR | Interner Serverfehler |
E5100 | 500 | SERVICE_ERROR | Fehler bei der Serviceverarbeitung |
E5200 | 500 | DAO_ERROR | Fehler beim Datenzugriff |
E5300 | 500 | ENGINE_ERROR | Fehler bei der Engine-Verarbeitung |
E5400 | 502 | EXTERNAL_SERVICE_ERROR | Aufruf eines externen Systems fehlgeschlagen |
E5500 | 503 | SERVICE_UNAVAILABLE | Dienst nicht verfügbar — Dies ist kein Urteil, sondern ein «Versuch fehlgeschlagen». Siehe Retry-After |
E5503 | 503 | SHUTTING_DOWN | Server wird heruntergefahren — an anderen Knoten übergeben oder Retry-After warten |
E5600 | 504 | TIMEOUT | Request-Timeout auf Serverseite |
E1900 (Rate-Limit) wird aufgelöst, wenn die Geschwindigkeit gedrosselt wird. E1101 (zu viele Authentifizierungsversuche) wird nicht durch Drosselung aufgelöst — hier stimmen die Anmeldedaten selbst nicht, und die IP ist gesperrt, bis das Zeitfenster schließt. Wenn beide im selben Backoff gebündelt werden, wird der zweite Fall in einer Endlosschleife aus Wiederholungsversuchen verbleiben.
400 bedeutet, dass die Anfrage nicht «interpretiert» werden konnte. 422 bedeutet, dass die Interpretation erfolgreich war, aber der «Inhalt» nicht den Regeln entspricht (ab 2026-08-31). Der Statuscode gibt an, ob das Format oder der Wert korrigiert werden muss — während Menschen die Nachricht lesen, bestimmt der Agent anhand des Statuscodes den nächsten Versuch.
Häufige Ablehnungen
| HTTP | reason | Bedeutung |
|---|---|---|
| 422 | asset_statement.eql.missing_columns | Das EQL-Statement liefert keine Pflichtfelder für diesen Typ — Anlagen-Statement |
| 422 | asset_statement.eql.unknown_type | Ungültiger Statement-Typ |
| 422 | alarm.invalid.alarm_config_id | Bei der Erstellung einer Alarm-Konfiguration wurde eine manuell vergebene ID eingegeben. Empfangen über GET /alarm/config/new-id |
| 409 | — | Versuch, eine zweite Band-Alarm-Konfiguration für denselben Tag zu erstellen. Nur eine Konfiguration pro Tag zulässig |
3.4 Extraktion von Fehlerinformationen
Da V5-Servicemethoden den Envelope nicht direkt exponieren, können Fehlerdetails über die folgenden zwei Methoden geprüft werden.
Methode 1 — Debug-Modus
APIClient_V5 client = new APIClient_V5(proto, host, port, user, token, true);
client.connect();
SiteResponseV5 result = client.site().create(req);
if (result == null) {
// 콘솔 로그에서 envelope 확인 가능
System.err.println("등록 실패 — 디버그 로그 확인");
}
Konsolenausgabe:
[V5] POST /api/v5/site body={...}
[V5] response: 400 {"_status":"ERROR","_code":"E1002","_message":"site_id must start with SITE_"}
Methode 2 — Verwendung der BaseServiceV5 static Methode (Custom Extension)
Der Helfer in BaseServiceV5 extrahiert Fehlerinformationen aus der JSONObject-Antwort. Dies ist nützlich bei der Erstellung eines Custom-Services, der die Bibliothek erweitert, um den Envelope zu erfassen.
import plantpulse.api.v5.service.BaseServiceV5;
import plantpulse.json.JSONObject;
// 환경 (envelope) JSONObject 가 있다면
String code = BaseServiceV5.getErrorCode(env); // 예: "E1002"
String message = BaseServiceV5.getErrorMessage(env); // 예: "validation failed: ..."
int httpStatus = BaseServiceV5.getHttpStatus(env); // 예: 400
3.5 Sichere Aufrufermuster
CRUD-Aufrufe
import plantpulse.api.v5.dto.request.SiteRequestV5;
import plantpulse.api.v5.dto.response.SiteResponseV5;
SiteRequestV5 req = new SiteRequestV5();
req.setSite_id("SITE_DJ");
req.setSite_name("대전공장");
SiteResponseV5 created = client.site().create(req);
if (created == null) {
// 실패 처리 — 예: 알람, 재시도, 다른 경로 시도
log.warn("사이트 생성 실패");
return;
}
System.out.println("등록 완료: " + created.getSite_id());
Listen-Aufrufe
List<SiteResponseV5> sites = client.site().list();
// 실패해도 null 이 아님 — 그대로 순회 가능
for (SiteResponseV5 s : sites) {
System.out.println(s.getSite_id());
}
if (sites.isEmpty()) {
log.info("등록된 사이트 없음");
}
Existenzprüfung → Abfrage
if (client.site().exists("SITE_DJ")) {
SiteResponseV5 site = client.site().get("SITE_DJ");
// ...
}
Order-Lebenszyklus (boolean-Rückgabe)
String orderId = "ORDER_000001";
if (!client.order().start(orderId)) {
// 전이 불가 (예: 이미 START 상태)
log.warn("작업 시작 실패 — 현재 상태 확인");
return;
}
// 작업 진행 ...
if (!client.order().end(orderId)) {
log.warn("작업 종료 실패");
}
3.6 Retry-Muster
V5 führt keine automatischen Retries durch. Falls Retries für temporäre Netzwerkfehler erforderlich sind, müssen diese manuell implementiert werden.
import java.util.function.Supplier;
public static <T> T callWithRetry(Supplier<T> apiCall, int maxRetries, long baseDelayMs) {
int attempt = 0;
while (true) {
T result = apiCall.get();
if (result != null) return result; // 성공
if (++attempt >= maxRetries) return null; // 포기
try {
Thread.sleep(baseDelayMs * attempt); // 지수 백오프
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return null;
}
}
}
// 사용
SiteResponseV5 site = callWithRetry(
() -> client.site().get("SITE_DJ"),
3,
1_000L);
503 · 429 und Retry-After
Wenn ein Aufruf aufgrund von Neustarts oder Überlastung momentan nicht möglich ist, wird ein «Retry-After»-Header in Retry-After (Ganzzahl in Sekunden) mitgesendet. Java-Clients halten diesen Header ein, warten jedoch standardmäßig nicht länger als 30 Sekunden. Bei direkten Aufrufen gilt Folgendes:
| Situation | Antwort |
|---|---|
| Web-Layer down (Neustart, Redeployment) — Proxy antwortet | 503 E5500, Retry-After: 15 |
| Server fährt herunter | 503 E5503, Retry-After: 15 |
| Backend-Speicher temporär nicht verfügbar (z. B. DB-Neustart) | 503 E5500, Retry-After: 15 |
| Request-Limit pro Token überschritten | 429 E1900, Retry-After berechnen das Limit |
| Max. Anzahl gleichzeitiger Streams überschritten | 429 E1900 oder 503 E5500, Retry-After |
- Lesezugriffe: Warten der angegebenen Zeit und erneuter Aufruf.
- Schreibzugriffe: Erneuter Aufruf mit gleichem
Idempotency-Key. Anfragen, die durch einen Neustart unterbrochen wurden, könnten bereits angewendet worden sein. - 500 deutet auf einen Defekt hin, 502
E5400auf einen fehlgeschlagenen externen Aufruf. In beiden Fällen gibt es keinRetry-After.
3.7 Unterscheidung zwischen leerer Antwort und null-Antwort
In V5 können null oder false folgende zwei Bedeutungen haben:
- Keine Daten auf dem Server (
NOT_FOUND,E1300) - Aufruf fehlgeschlagen (Netzwerkfehler, Validierungsfehler etc.)
Um diese zu unterscheiden, verwenden Sie bitte exists().
if (!client.site().exists("SITE_DJ")) {
log.info("사이트가 등록되어 있지 않습니다");
// 신규 등록 로직
} else {
SiteResponseV5 site = client.site().get("SITE_DJ");
if (site == null) {
log.error("사이트는 존재하지만 조회 실패 — 일시적 오류 가능");
}
}
Nächste Schritte
- System-Service — Health-Check
- Site-Service — Startpunkt für Stammdaten