Net-Base Magasin

09.06.2026

REST API med RemObjects SDK: ryddig versjonering og feilsøking av JSON-endepunkter (Delphi Source-utdrag)

Hvordan du med RemObjects SDK i Delphi bygger et REST API som ikke svikter i drift: stabile JSON-kontrakter, versjonering uten URL-kaos, Correlation-ID gjennom alle lag, sentralisert Error-Mapping, Snapshot-logging for krevende debug-tilfeller samt praksisnære anbefalinger...

09.06.2026

Fra magasinetema til prosjektpraksis

Egnede tjeneste- og tekniske sider for innlegget

Warum „REST API mit RemObjects SDK“ in der Praxis oft an den Rändern entscheidet

Eine REST API mit RemObjects SDK steht und fällt selten am „Hello World“-Service, sondern an den Stellen, an denen Betrieb, Legacy und Integration aufeinanderprallen: Versionierung ohne Stillstand, konsistentes Fehlerverhalten über alle Endpunkte, reproduzierbares Debugging bei Proxy-Ketten und die Fähigkeit, Requests im Problemfall eindeutig zu korrelieren.

RemObjects SDK bringt dafür viel Infrastruktur mit: Services, Message-Formate, Serialisierung, Hosting (z. B. als Windows- und Linux-Services oder hinter IIS/Reverse Proxy) und definierte Stellen, um Fehler zentral zu behandeln. Was in gewachsenen Business-Software-Landschaften aber häufig fehlt, ist ein konsequent durchgezogener Vertrag: Welche JSON-Felder sind stabil? Wie signalisieren wir Fehler? Wie erkennen wir einen Request wieder, wenn er durch Load Balancer, TLS-Termination und mehrere Backend-Schichten gelaufen ist?

Der folgende Ansatz (inklusive Delphi-Snipsel) zeigt eine robuste Linie für RemObjects SDK: JSON-Verträge versionieren, Correlation-ID (Request-ID zur Nachverfolgung) erzwingen, Exceptions in HTTP-Status und JSON-Fehlerobjekte übersetzen und dabei Debugging und Betrieb nicht gegeneinander ausspielen. Zusätzlich schauen wir auf Randfälle, die in echten Umgebungen regelmäßig auftreten: Threading im Server, Datenbank-Zugriffe mit BDE-Ablosung mit nativer Anbindung, Proxy-Header, Timeouts und „schmutzige“ Client-Payloads.

Architektur-Entscheidung: Versionierung über Medien-Typ statt URL

Viele APIs versionieren über Pfade wie /v1/. Das ist pragmatisch, aber in länger laufenden Integrationen (z. B. ERP/DMS/CRM-Anbindungen) führt es oft zu URL-Duplizierung, doppelten Routen, doppelten Tests und „Welche Version nutzen wir eigentlich?“ in Betriebshandbüchern.

Eine Alternative ist Versionierung über den Media Type (Content Negotiation). Der Client sendet z. B. Accept: application/vnd.company.order+json;v=2. Der Server liest die Version deterministisch aus und passt Contract/DTO-Verhalten an. Das funktioniert in Proxy- und Cache-Ketten, wenn die Header sauber weitergereicht werden. Für Admins ist es zudem gut prüfbar: Ein Request lässt sich per Curl/Postman reproduzieren, ohne dass sich URLs unterscheiden.

RemObjects SDK ist nicht „REST-puristisch“, sondern ein pragmatisches Service-Framework. Genau deshalb lohnt sich die Medien-Typ-Variante: Sie können stabile Endpunkte behalten und dennoch Verträge weiterentwickeln. Wichtig ist, dass Sie die Version immer auswerten, an einer Stelle zentral entscheiden und das Ergebnis in Ihren Service-Kontext übernehmen.

Wann kippt die Accept-Header-Variante?

In der Praxis gibt es drei typische Bruchstellen, die man vorab adressieren sollte:

  • Proxy-Policies: Manche Reverse Proxies/WAF-Regeln normalisieren oder filtern Accept-Header. Dann fällt Ihre API still auf Default zurück. Lösung: Proxy-Regeln explizit prüfen, ggf. auf X-Api-Version ausweichen.
  • Client-Libraries: Einige HTTP-Clients setzen eigene Accept-Header und überschreiben Werte. Lösung: Contract-Version auch als optionalen Query-Parameter unterstützen (nur als Fallback), oder den Accept-Header serverseitig tolerant parsen.
  • Caching: Wenn Response-Caching im Spiel ist, muss der Cache nach Accept variieren (Vary: Accept), sonst liefert er Version 1 an Version-2-Clients. Lösung: Vary bewusst setzen, oder Caching auf API-Ebene deaktivieren.
  • Kildeutdrag: Request-kontekst, korrelasjons-ID, versjon og feilmapping

    Koden er bevisst utformet slik at den kan integreres i eksisterende RemObjects-Serverprojekte: et lite kontekstlag, en parser for API-versjonen (fra Accept), en korrelasjons-ID-mekanisme og et sentralt Exception-Mapping. Begreper:

    • Correlation-ID: En unik ID per forespørsel som returneres i responsen og refereres i logger.
    • Exception-Mapping: Oversettelse av interne Delphi-Exceptions til stabile, klientbehandlelige feilobjekter (inkl. HTTP-status).
    • Contract-Version: Versjon av JSON-kontrakten som styrer oppførsel og felter.
    Delphi
    unit Api.Infrastructure;
    
    interface
    
    uses
      System.SysUtils, System.Classes, System.StrUtils, System.Generics.Collections,
      System.JSON;
    
    type
      EApiError = class(Exception)
      private
        FHttpStatus: Integer;
        FCode: string;
        FCorrelationId: string;
      public
        constructor Create(const AHttpStatus: Integer; const ACode, AMessage, ACorrelationId: string);
        property HttpStatus: Integer read FHttpStatus;
        property Code: string read FCode;
        property CorrelationId: string read FCorrelationId;
      end;
    
      TApiContext = record
        CorrelationId: string;
        ContractVersion: Integer;
        RemoteIp: string;
        UserAgent: string;
        class function New: TApiContext; static;
      end;
    
      TApiVersion = record
        class function FromAcceptHeader(const AAccept: string; const ADefault: Integer = 1): Integer; static;
      end;
    
      TApiErrorMapper = class
      public
        class function ToErrorJson(const E: Exception; const ACorrId: string): TJSONObject; static;
        class function ToHttpStatus(const E: Exception): Integer; static;
        class function SafeMessage(const E: Exception): string; static;
      end;
    
    implementation
    
    { EApiError }
    
    constructor EApiError.Create(const AHttpStatus: Integer; const ACode, AMessage, ACorrelationId: string);
    begin
      inherited Create(AMessage);
      FHttpStatus := AHttpStatus;
      FCode := ACode;
      FCorrelationId := ACorrelationId;
    end;
    
    { TApiContext }
    
    class function TApiContext.New: TApiContext;
    begin
      Result.CorrelationId := '';
      Result.ContractVersion := 1;
      Result.RemoteIp := '';
      Result.UserAgent := '';
    end;
    
    { TApiVersion }
    
    class function TApiVersion.FromAcceptHeader(const AAccept: string; const ADefault: Integer): Integer;
    // Erwartet z.B.: application/vnd.company.order+json;v=2
    var
      Parts: TArray<string>;
      P: string;
      V: string;
      I: Integer;
    begin
      Result := ADefault;
      if AAccept.Trim.IsEmpty then
        Exit;
    
      Parts := AAccept.Split([';', ',']);
      for P in Parts do
      begin
        V := Trim(P);
        if StartsText('v=', V) then
        begin
          if TryStrToInt(Copy(V, 3, MaxInt), I) and (I > 0) and (I < 100) then
            Exit(I);
        end;
      end;
    end;
    
    { TApiErrorMapper }
    
    class function TApiErrorMapper.SafeMessage(const E: Exception): string;
    // Im Betrieb keine internen Details, keine SQL, keine Pfade.
    // Für Debug/Stage kann man das über Konfiguration erweitern.
    begin
      if E is EApiError then
        Exit(E.Message);
    
      if E is EArgumentException then
        Exit('Ungültige Parameter.');
    
      Exit('Interner Fehler.');
    end;
    
    class function TApiErrorMapper.ToHttpStatus(const E: Exception): Integer;
    begin
      if E is EApiError then
        Exit(EApiError(E).HttpStatus);
    
      if E is EArgumentException then
        Exit(400);
    
      Exit(500);
    end;
    
    class function TApiErrorMapper.ToErrorJson(const E: Exception; const ACorrId: string): TJSONObject;
    var
      Code: string;
      Status: Integer;
      Msg: string;
    begin
      Status := ToHttpStatus(E);
      Msg := SafeMessage(E);
    
      if E is EApiError then
        Code := EApiError(E).Code
      else if E is EArgumentException then
        Code := 'bad_request'
      else
        Code := 'internal_error';
    
      Result := TJSONObject.Create;
      Result.AddPair('error', TJSONObject.Create
        .AddPair('code', Code)
        .AddPair('message', Msg)
        .AddPair('httpStatus', TJSONNumber.Create(Status))
        .AddPair('correlationId', ACorrId));
    end;
    
    end.

    Zweck: Stabiler Request-Kontext statt „irgendwo im Threadlocal“

    Der Snipsel trennt bewusst: TApiContext ist der minimale Zustand, den Sie durchreichen wollen. In RemObjects SDK läuft viel über Server-/Channel-Kontext. In heterogenen Projekten (z. B. zusätzliche Worker-Threads, DB-Queue, Hintergrundjobs) ist explizites Durchreichen aber oft robuster als implizite Threadlocals, weil Sie damit Nebenläufigkeit und Kontextwechsel sichtbarer machen.

    Forutsetninger: Die Accept-Header-Variante setzt voraus, dass Ihr Reverse Proxy (nginx, IIS ARR, Traefik) den Header unverändert weiterreicht. In manchen Umgebungen werden „ungewöhnliche“ Accept-Header gefiltert oder zusammengefasst.

    Fallgruver: Versionierung über Accept ist nur so gut wie Ihre Tests. Wenn Clients Libraries nutzen, die Accept überschreiben, kann eine API plötzlich auf Default zurückfallen. Für Legacy-Clients ist ein Default-Fallback sinnvoll, aber er muss in Monitoring sichtbar sein (z. B. Log-Warnung „Version defaulted“).

    Varianten: Wenn Sie Versionierung lieber über X-Api-Version machen: Der Parser ist identisch, nur die Quelle ist ein anderer Header. Aus Sicht von Gateways ist das manchmal einfacher zu kontrollieren.

    Integration in RemObjects SDK: Correlation-ID und Exception-Mapping am Service-Einstieg

    Die eigentliche Wirkung entsteht, wenn Sie die Mechanik konsequent am Rand Ihres Servers anwenden: einmal am Request-Einstieg aus Headern lesen, einmal am Exception-Ausgang in eine stabile Response übersetzen. Je nach Hosting (z. B. RO-HTTP-Server, IIS-Hosting, selbst betriebener Windows-/Windows- und Linux-Services) unterscheiden sich die konkreten Hook-Punkte; das Prinzip bleibt gleich: Context bauen, Business-Logik aufrufen, Exceptions zentral mappen.

    In RemObjects-Projekten wird häufig pro Service-Methode direkt gearbeitet. Das skaliert anfangs gut, kippt aber bei Betrieb: Jede Methode baut Logging und Fehlerbehandlung anders. Ein sauberer Schnitt ist eine Service-Basis oder ein Dispatcher, der standardisiert.

    Praktischer Ablauf (bewusst kurz und implementierungsnah)

    1. Correlation-ID aus Request-Header X-Correlation-ID lesen; wenn fehlt, serverseitig erzeugen (z. B. GUID).
    2. Contract-Version aus Accept lesen (oder aus X-Api-Version).
    3. Request-Start loggen: Methode, Pfad, Correlation-ID, Remote IP, Dauer-Messung starten.
    4. Business-Logik ausführen; DB-Zugriffe möglichst transaktional kapseln.
    5. Exception abfangen: HTTP-Status bestimmen, JSON-Fehlerobjekt erzeugen, Response-Header X-Correlation-ID setzen.
    6. Request-Ende loggen: Status, Dauer, ggf. Fehlercode.

    Threading im Server: Warum Correlation-ID ohne Kontext-Disziplin wertlos wird

    Ein häufiger Delphi-Randfall: Die Service-Methode triggert asynchrone Arbeit (z. B. Report-Generierung, Import, Push in ein DMS). Dann ist der ursprüngliche Request-Thread nicht mehr der, der später Logzeilen schreibt. Wenn die Correlation-ID nur „am Anfang“ bekannt ist, zerfällt die Nachverfolgbarkeit.

    Pragmatische Regel: Alles, was nicht strikt im Request-Thread bleibt, bekommt den Context explizit übergeben. Auch wenn das nach mehr Parameterlisten aussieht, zahlt es sich aus. Alternativ kann man mit einem klar definierten Kontext-Objekt arbeiten, das bewusst an Worker übergeben wird (statt globaler Variablen oder versteckter Singletons).

    Typische Kipppunkte in RemObjects-/Delphi-Servern:

    • DB-Connections pro Thread: BDE-Ablosung mit nativer Anbindung-Verbindungen sind nicht automatisch thread-sicher teilbar. Ein Connection-Pool oder pro Thread eine Verbindung ist häufig sinnvoller als „eine globale Connection“.
    • Transaktionsgrenzen: Wenn Sie innerhalb eines Requests mehrere Schritte haben, die zusammengehören, muss die Transaktion in der gleichen logischen Einheit bleiben. Asynchrone Arbeit darf nicht „aus Versehen“ in der gleichen Transaktion weiterlaufen.
    • Cancellation: Wenn der Client abbricht (Proxy timeout, Browser closed), läuft der Server oft weiter. Überlegen Sie bewusst, ob Hintergrundarbeit dann noch Sinn ergibt.

    Datenzugriff und Fehlercodes: 409 ist nicht „auch ein 500“

    In Integrationsprojekten ist sauberes Error-Mapping mehr als Kosmetik. Es entscheidet, ob ein Gegenüber (ERP-Connector, ETL-Job, Kundenportal) korrekt reagieren kann. Ein paar praxisnahe Leitplanken, die sich in Delphi/RemObjects-Umgebungen bewährt haben:

    • 400 Bad Request: Validierung, fehlende/ungültige Parameter, JSON nicht parsebar. Wichtig: Die Antwort soll stabil bleiben, auch wenn der Body kaputt ist.
    • 401/403: Authentifizierung vs. Berechtigung trennen. 401 bedeutet „keine/ungültige Identität“, 403 „Identität ok, aber verboten“.
    • 404: Ressource existiert nicht. Vorsicht bei Security: Nicht immer verraten, ob etwas existiert.
    • 409 Conflict: Fachlicher Konflikt (z. B. Versionskonflikt, „Status erlaubt diese Aktion nicht“, eindeutige Schlüsselverletzung, wenn sie fachlich relevant ist).
    • 422 Unprocessable Content: Wenn syntaktisch alles ok ist, aber fachliche Validierung scheitert (nicht jedes Team nutzt 422, aber es ist oft klarer als 400).
    • 500: Alles, was Sie nicht sauber klassifizieren können. Dazu gehört auch „DB down“, „Timeout“, „Unhandled Exception“.

    Delphi-spezifischer Kniff: Viele DB-Fehler kommen als generische Exceptions hoch. Es lohnt sich, an der Datenzugriffsschicht gezielt auf bekannte Situationen zu prüfen und sie in EApiError zu überführen. Wichtig dabei: Keine SQL-Fragmente oder internen Tabellen-/Spaltennamen in die Client-Message übernehmen. Diese Details gehören ins Log, nicht in die Response.

    Debugging-Kniff: reproduzierbare Fehler durch „Contract Snapshot“

    Ungewöhnlich, aber im Betrieb extrem hilfreich: Speichern Sie bei Fehlern (oder gezielt bei bestimmten Correlation-IDs) einen „Snapshot“ aus Request-Headern + Request-Body in einer Debug-Spool-Datei. Das ist kein Dauerlogging (Datenschutz/Volumen), sondern ein kontrolliertes Werkzeug, um schwer reproduzierbare Fälle aus Produktionsnähe nachzustellen.

    Wichtig: Ein Snapshot darf niemals ungefiltert Auth-Header, Tokens oder personenbezogene Daten persistieren. In der Praxis bedeutet das: Redaction (Maskierung) und Aktivierung nur über Feature-Flag oder Whitelist (z. B. nur für bestimmte Correlation-IDs, kurze Zeitfenster).

    Saubere Umsetzung in der Praxis: Maskieren statt Weglassen

    In echten Integrationen sind gerade die „kritischen“ Felder oft die, die man zum Debuggen bräuchte (z. B. Identifikatoren). Statt pauschalem Weglassen ist Maskieren besser: Token teilweise ersetzen, E-Mail nur Domain behalten, IBAN nur die letzten Ziffern. So bleibt der Fall reproduzierbar, ohne unnötige Daten im Dateisystem zu verteilen. Zusätzlich sollte der Snapshot klar als Debug-Artefakt gekennzeichnet sein und eine definierte Aufbewahrungszeit haben.

    Sikkerhet og drift: header-videreføring, proxy-kjeder og timeouts

    En REST API slutter sjelden direkte ved klienten. Vanlig er kjeder av reverse proxy, TLS-terminering, WAF eller API-gateway. Dette gir praktiske punkter:

    • Remote IP: Ikke stol blindt på X-Forwarded-For. Ta bare over fra betrodde proxier, ellers bruk direkte socket-IP. I driftsdokumentasjonen bør det stå hvilke hopp som er «trusted».
    • Timeouts: Hvis proxyen har 30 sekunder, men backend trenger 2 minutter, skaper dere ghost-requests. Sett timeouts konsistent langs kjeden og avgjør: synkront request eller jobbmønster (202 Accepted + status-endepunkt).
    • Correlation-ID: Sett Correlation-ID i response-headere, slik at administratorer kan korrelere den mellom logger og klientside. Hvis et gateway bruker egne request-IDer: loggfør og kartlegg begge IDene.
    • Feiltekster: Ingen interne detaljer i produksjon. Debug-detaljer kun kontrollert (Stage/Feature-Flag) og ved tvil kun i loggen.

    Vurdering: Hvorfor RemObjects SDK kan være en fordel her

    I Delphi-økosystemer bygges REST-server ofte med lettere rammeverk (f.eks. minimalistiske HTTP-routere). RemObjects SDK viser sin styrke når dere allerede har — eller trenger — en flerlagsarkitektur:

    • Tydelige tjenestegrenser: Tjenestemetoder er eksplisitte, kontrakter kan versjoneres.
    • Transports og serialisering: Dere kan bruke JSON, men også andre meldingsformater (avhengig av oppsett), uten å blande inn faglogikken.
    • Drift: Hosting-opsjoner og integrasjon i eksisterende Windows- og Linux-tjenester er planbare, inkludert ryddige rollouts.

    Den viste tilnærmingen supplerer dette med de delene som ofte mangler i hverdagen: enhetlige feilobjekter, deterministisk versjonering og korrelerbart logging. Spesielt for individuell bedriftsprogramvare med lange livssykluser sparer dette tid ved oppdateringer og ved integrasjon av eksterne systemer.

    Konklusjon: Lønner innsatsen seg — og hvor blir tilnærmingen uhensiktsmessig?

    Merverdien oppstår når deres REST-grensesnitt ikke bare «fungerer», men er driftbart over tid: stabile JSON-kontrakter, versjonering uten URL-villvoksing, etterprøvbare feil og debugging uten gjetting. Nettopp der er tilnærmingen med Context, Correlation-ID og sentralt Exception-Mapping i RemObjects SDK sterk.

    Bruksgrenser: Hvis dere bare har ett enkelt, kortlivet endepunkt uten integrasjonspartnere, fremstår media-type-versjonering fort som overengineering. Også snapshot-logging er bare fornuftig hvis dere disiplinert implementerer Redaction og aktivering. Og: Hvis proxy-stakken deres «optimaliserer» eller fjerner headere, må dere først rette opp infrastrukturen, ellers debugger dere feil lag.

    Hvis dere skal modernisere en eksisterende Delphi-serverlandskap eller må integrere en prosessnær programvareløsning ryddig i ERP/DMS/CRM, er nettopp disse mekanismene ofte forskjellen mellom «kjører i test» og «kjører i drift».

    I det faglige miljøet spiller også Delphi REST-API og REST-Server og Remobjects Sdk Delphi en viktig rolle når integrasjoner, dataflyt og videreutvikling må fungere ryddig sammen.

    Diskutere prosjekt eller moderniseringsprosjekt med Net-Base.

    Neste steg

    Når et tema blir et reelt prosjekt, bør arkitektur, eksisterende systemer og drift tidlig vurderes samlet.

    Vi bistår ikke bare med enkeltspørsmål, men også når kodesnutter, legacy-temaer eller portalideer skal utvikles til et robust virksomhetsprosjekt.

    • Eksisterende tilstand, målbildet og tekniske risikoer vurderes samlet.
    • REST, datatilgang, portaler og utrulling blir ikke utsatt som sene følger.
    • Dere ser tidlig hvilken vei som er økonomisk og driftsmessig levedyktig.

    Del innlegg

    Del dette innlegget direkte

    LinkedIn, X, XING, Facebook, WhatsApp og e‑post er umiddelbart tilgjengelig. For Instagram forbereder vi lenke og kort tekst umiddelbart.

    E-post

    Instagram åpnes i en ny fane. Lenken og kortteksten kopieres først til utklippstavlen.