Zum Hauptinhalt springen

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​

MethodenmusterRückgabe bei ErfolgRückgabe bei Fehler
create(req) / update(id, req) / get(id)*ResponseV5 Instanznull
list()-SerieList<*ResponseV5>Leere Liste (niemals null)
delete(id)truefalse
exists(id)true (vorhanden)false (nicht vorhanden oder Fehler)
count()-Serielong (≥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/_message eingesehen 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.

CodeHTTPNameTypische Ursache
E1000400INVALID_REQUESTSyntaxfehler, Typenkonflikt
E1001400MISSING_PARAMETERFehlender Pflichtparameter
E1002422VALIDATION_FAILEDVerstoß gegen Wertebereich-, Format- oder Domain-Regeln (z. B. ID-Präfix)
E1100401UNAUTHORIZEDapi_key fehlt oder ungültig
E1101429AUTH_TOO_MANY_ATTEMPTSAkkumulierte Authentifizierungsfehler von derselben IP — siehe Retry-After
E1102401TOKEN_EXPIREDToken abgelaufen — Wiederherstellung durch Neuausstellung möglich
E1103401TOKEN_REVOKEDToken entzogen — derselbe Token kann nicht wiederhergestellt werden
E1104401TOKEN_IP_MISMATCHAufruf-IP weicht von der an den Token gebundenen IP ab
E1200403FORBIDDENAuthentifiziert, aber keine Berechtigung
E1300404NOT_FOUNDEntsprechende ID nicht vorhanden
E1400405METHOD_NOT_ALLOWEDNicht zulässige Methode
E1500409CONFLICTRessourcenkonflikt
E1501409DUPLICATE_ENTITYEntität existiert bereits
E1600413PAYLOAD_TOO_LARGERequest-Body zu groß
E1700415UNSUPPORTED_MEDIA_TYPENicht unterstützter Medientyp
E1800422IDEMPOTENCY_CONFLICTGleiche(r) Idempotency-Key + anderer Body
E1801409IDEMPOTENCY_IN_PROGRESSGleicher Schlüssel wird gerade verarbeitet — bitte später erneut versuchen
E1900429RATE_LIMIT_EXCEEDEDRate-Limit überschritten
E5000500INTERNAL_ERRORInterner Serverfehler
E5100500SERVICE_ERRORFehler bei der Serviceverarbeitung
E5200500DAO_ERRORFehler beim Datenzugriff
E5300500ENGINE_ERRORFehler bei der Engine-Verarbeitung
E5400502EXTERNAL_SERVICE_ERRORAufruf eines externen Systems fehlgeschlagen
E5500503SERVICE_UNAVAILABLEDienst nicht verfügbar — Dies ist kein Urteil, sondern ein «Versuch fehlgeschlagen». Siehe Retry-After
E5503503SHUTTING_DOWNServer wird heruntergefahren — an anderen Knoten übergeben oder Retry-After warten
E5600504TIMEOUTRequest-Timeout auf Serverseite
Es gibt zwei 429er — eine Vermischung verhindert die Wiederherstellung

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.

Unterscheidung zwischen 400 und 422

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​

HTTPreasonBedeutung
422asset_statement.eql.missing_columnsDas EQL-Statement liefert keine Pflichtfelder für diesen Typ — Anlagen-Statement
422asset_statement.eql.unknown_typeUngültiger Statement-Typ
422alarm.invalid.alarm_config_idBei 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:

SituationAntwort
Web-Layer down (Neustart, Redeployment) — Proxy antwortet503 E5500, Retry-After: 15
Server fährt herunter503 E5503, Retry-After: 15
Backend-Speicher temporär nicht verfügbar (z. B. DB-Neustart)503 E5500, Retry-After: 15
Request-Limit pro Token überschritten429 E1900, Retry-After berechnen das Limit
Max. Anzahl gleichzeitiger Streams überschritten429 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 E5400 auf einen fehlgeschlagenen externen Aufruf. In beiden Fällen gibt es kein Retry-After.

3.7 Unterscheidung zwischen leerer Antwort und null-Antwort​

In V5 können null oder false folgende zwei Bedeutungen haben:

  1. Keine Daten auf dem Server (NOT_FOUND, E1300)
  2. 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​