Net-Base Magazine

09.06.2026

REST API met RemObjects SDK: JSON-eindpunten consequent versioneren en debuggen (Delphi broncodefragmenten)

Hoe u met RemObjects SDK in Delphi een REST API opbouwt die in productie niet faalt: stabiele JSON-contracten, versionering zonder URL-wildgroei, Correlation-ID door alle lagen heen, centraal foutmapping, snapshot-logging voor lastige debuggevallen en praktijkgerichte aanwijzingen...

09.06.2026

Van magazinethema naar projectpraktijk

Relevante dienst- en technische pagina's bij het artikel

Waarom „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-Ablösung 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.
  • Broncodefragment: Request-Context, Correlation-ID, versie en Error-Mapping

    De code is bewust zo gesneden dat hij in bestaande RemObjects-Serverprojecten te integreren is: een kleine contextlaag, een parser voor de API-versie (uit Accept), een Correlation-ID-mechanisme en een centraal Exception-Mapping. Begrippen:

    • Correlation-ID: Eenduidige ID per request, die in de response terug verschijnt en in logs wordt gerefereerd.
    • Exception-Mapping: Vertaling van interne Delphi-Exceptions naar stabiele, door de client verwerkbare foutobjecten (incl. HTTP-status).
    • Contract-Version: Versie van het JSON-contract die gedrag en velden stuurt.
    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;
    // Verwacht bijv.: 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;
    // In productie geen interne details, geen SQL, geen paden.
    // Voor debug/stage kan dit via configuratie uitgebreid worden.
    begin
      if E is EApiError then
        Exit(E.Message);
    
      if E is EArgumentException then
        Exit('Ongeldige parameters.');
    
      Exit('Interne fout.');
    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.

    Doel: stabiele Request-context in plaats van „irgendwo im Threadlocal“

    De snippet scheidt bewust: TApiContext is de minimale toestand die u wilt doorgeven. In RemObjects SDK loopt veel via server-/channel-context. In heterogene projecten (bijv. extra worker-threads, DB-queue, achtergrondjobs) is expliciet doorgeven echter vaak robuuster dan impliciete threadlocals, omdat u daarmee gelijktijdigheid en contextwisselingen zichtbaarder maakt.

    Randvoorwaarden: De Accept-header-variant veronderstelt dat uw reverse proxy (nginx, IIS ARR, Traefik) de header onveranderd doorgeeft. In sommige omgevingen worden „ongebruikelijke“ Accept-headers gefilterd of samengevoegd.

    Valkuilen: Versionering via Accept is alleen zo goed als uw tests. Als clients libraries gebruiken die Accept overschrijven, kan een API plots op de default terugvallen. Voor legacy-clients is een default-fallback nuttig, maar die moet zichtbaar zijn in de monitoring (z. B. Log-Warnung „Version defaulted“).

    Varianten: Als u versiebeheer liever via X-Api-Version doet: de parser is identiek, alleen de bron is een andere header. Vanuit gateways is dat soms eenvoudiger te controleren.

    Integratie in RemObjects SDK: Correlation-ID en Exception-Mapping bij de ingang van der service

    Het daadwerkelijke effect ontstaat wanneer u de mechaniek consequent aan de rand van uw server toepast: eenmaal bij de request-invoer uit headers lezen, eenmaal bij het exception-uitgang in een stabiele response vertalen. Afhankelijk van hosting (bijv. RO-HTTP-Server, IIS-Hosting, zelf beheerde Windows-/Windows- en Linux-services) verschillen de concrete hook-punten; het principe blijft hetzelfde: context bouwen, business-logica aanroepen, exceptions centraal mappen.

    In RemObjects-projecten wordt vaak per service-methode direct gewerkt. Dat schaalt aanvankelijk goed, maar faalt in de operatie: elke methode bouwt logging en foutafhandeling op een andere manier. Een duidelijke scheiding is een service-basis of een dispatcher die standaardiseert.

    Praktische procedure (bewust kort en implementatienabij)

    1. Lees de Correlation-ID uit de request-header X-Correlation-ID; als die ontbreekt, server-side genereren (bijv. GUID).
    2. Lees de contractversie uit Accept (of uit X-Api-Version).
    3. Log request-start: methode, pad, Correlation-ID, remote IP, start duurmeting.
    4. Voer business-logica uit; kapsel DB-toegangen bij voorkeur transactioneel.
    5. Vang exceptions op: bepaal HTTP-status, genereer JSON-foutobject, zet response-header X-Correlation-ID.
    6. Log request-einde: status, duur, eventueel foutcode.

    Threading op de server: waarom Correlation-ID zonder contextdiscipline waardeloos wordt

    Een veelvoorkomend Delphi-randgeval: de service-methode triggert asynchroon werk (bijv. rapportgeneratie, import, push naar een DMS). Dan is de oorspronkelijke request-thread niet meer degene die later logregels schrijft. Als de Correlation-ID alleen „aan het begin“ bekend is, valt de traceerbaarheid uiteen.

    Pragmatische regel: alles wat niet strikt in de request-thread blijft, krijgt de context expliciet doorgegeven. Ook al oogt dat als meer parameterlijsten, het betaalt zich terug. Als alternatief kunt u met een helder gedefinieerd contextobject werken dat bewust aan workers wordt doorgegeven (in plaats van globale variabelen of verborgen singletons).

    Typische knelpunten in RemObjects-/Delphi-servers:

    • DB-verbindingen per 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“.
    • Transactiegrenzen: 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.
    • Annulering: Wenn der Client abbricht (Proxy timeout, Browser closed), läuft der Server oft weiter. Überlegen Sie bewusst, ob Hintergrundarbeit dann noch Sinn ergibt.

    Gegevensaccess en foutcodes: 409 is niet „ook een 500“

    In integratieprojecten is zuivere foutmapping meer als cosmetiek. Het bepaalt of een tegenpartij (ERP-Connector, ETL-Job, klantenportaal) correct kan reageren. Een paar praktische richtlijnen, die zich in Delphi/RemObjects-omgevingen hebben bewezen:

    • 400 Bad Request: Validatie, ontbrekende/ongeldige parameters, JSON niet parseerbaar. Belangrijk: het antwoord moet stabiel blijven, ook als de body corrupt ist.
    • 401/403: Authenticatie en autorisatie scheiden. 401 bedeutet „keine/ungültige Identität“, 403 „Identität ok, aber verboten“.
    • 404: Resource bestaat niet. Wees terughoudend bij beveiliging: niet altijd prijsgeven, of iets bestaat.
    • 409 Conflict: Functioneel conflict (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 „databaseuitval“, „Timeout“, „onbehandelde Ausnahme“.

    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.

    Debuggingtip: reproduceerbare fouten 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 Produktienä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).

    Consistente 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.

    Beveiliging en operatie: header-doorvoer, proxy-ketens en timeouts

    Een REST API eindigt zelden direct bij de client. Typisch zijn ketens van reverse proxy, TLS-terminatie, WAF of API-gateway. Daaruit volgen praktische aandachtspunten:

    • Remote IP: Vertrouw niet blind op X-Forwarded-For. Neem alleen over van vertrouwde proxies en gebruik anders de directe socket-IP. In beheerhandleidingen moet staan welke hops „trusted“ zijn.
    • Timeouts: Als een proxy 30 seconden heeft, uw backend echter 2 minuten nodig heeft, veroorzaakt u ghost-requests. Stel timeouts consequent in langs de keten en beslis: synchroon request of job-patroon (202 Accepted + status-endpoint).
    • Correlation-ID: Zet de correlation-ID in response-headers zodat beheerders deze uit logs en clientzijde kunnen samenvoegen. Als een gateway eigen request-IDs gebruikt: log en koppel beide IDs.
    • Foutteksten: In de productieomgeving geen interne details. Debug-details alleen gecontroleerd (stage/feature-flag) en bij twijfel alleen in de logs.

    Plaatsing: Waarom RemObjects SDK hierin voordeel kan bieden

    In Delphi-ecosystemen worden REST-servers vaak met lichter frameworks (bijv. minimalistische HTTP-router) gebouwd. RemObjects SDK speelt zijn kracht uit wanneer u al een meerlaagse architectuur heeft of deze nodig heeft:

    • Duidelijke servicegrenzen: Servicemethoden zijn expliciet, contracts zijn versioneerbaar.
    • Transports en serialisatie: U kunt JSON spreken, maar ook andere berichtformaten (afhankelijk van de setup), zonder de domeinlogica te vervuilen.
    • Operatie: Hostingopties en integratie in bestaande Windows- en Linux-services zijn planbaar, inclusief nette rollouts.

    De getoonde aanpak vult dat aan met de onderdelen die in de dagelijkse praktijk vaak ontbreken: uniforme foutobjecten, deterministische versionering en correleerbaar loggen. Juist bij individuele bedrijfssoftware met lange levenscycli bespaart u daarmee tijd bij updates en bij de integratie van externe systemen.

    Conclusie: Loont de inspanning — en waar kantelt de aanpak?

    De meerwaarde ontstaat wanneer uw REST-interface niet alleen „werkt“, maar duurzaam beheersbaar is: stabiele JSON-contracten, versiebeheer zonder URL-wildgroei, verklaarbare fouten en debuggen zonder giswerk. Precies daar is de aanpak met Context, Correlation-ID en centraal exception-mapping in RemObjects SDK sterk.

    Toepassingsgrenzen: Als u slechts één enkel, kortlevend endpoint zonder integratiepartners heeft, voelt media-type-versionering snel als over-engineering. Ook snapshot-logging is alleen zinvol als u redaction en activering gedisciplineerd implementeert. En: als uw proxy-stack headers „optimaliseert“ of verwijdert, moet u eerst de infrastructuur rechtzetten, anders debugt u de verkeerde laag.

    Als u een bestaande Delphi-serverlandschap moderniseert of een procesgebonden softwareoplossing netjes in ERP/DMS/CRM moet integreren, zijn juist deze mechanismen vaak het verschil tussen „loopt in de test“ en „loopt in de operatie“.

    In het vakinhoudelijke domein spelen ook Delphi REST-API en REST-Server en Remobjects Sdk Delphi een belangrijke rol, wanneer integraties, gegevensstromen en doorontwikkeling naadloos moeten samenwerken.

    Project of moderniseringsproject met Net-Base bespreken.

    volgende stap

    Wanneer het onderwerp een concreet project wordt, moeten architectuur, bestaande omgeving en exploitatie vroegtijdig samen worden bekeken.

    We ondersteunen niet alleen bij individuele vragen, maar ook wanneer uit broncodefragmenten, legacy-onderwerpen of portalideeën een robuust bedrijfsproject moet ontstaan.

    • Huidige situatie, doelbeeld en technische risico's worden gezamenlijk beoordeeld.
    • REST, toegang tot gegevens, portalen en rollout worden niet naar latere fasen verschoven.
    • U ziet vroeg welke weg economisch en operationeel levensvatbaar is.

    Bericht delen

    Dit bericht direct delen

    LinkedIn, X, XING, Facebook, WhatsApp en e-mail zijn direct beschikbaar. Voor Instagram bereiden we de link en een korte tekst direct voor.

    E-mail

    Instagram opent in een nieuw tabblad. Link en korte tekst worden van tevoren naar het klembord gekopieerd.