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 met RemObjects SDK“ in de praktijk vaak aan de randen beslist

Een REST API met RemObjects SDK staat zelden of nooit of valt niet met de „Hello World“-service, maar op de plekken waar operatie, Legacy en integratie op elkaar botsen: versiebeheer zonder stilstand, consistent foutgedrag over alle eindpunten, reproduceerbare foutopsporing bij proxyketens en het vermogen om requests in geval van problemen eenduidig te correleren.

RemObjects SDK brengt veel infrastructuur mee: services, message-formaten, serialisatie, hosting (bijv. als Windows- en Linux-services of achter IIS/Reverse Proxy) en gedefinieerde plekken om fouten centraal af te handelen. Wat in gegroeide business-softwarelandschappen echter vaak ontbreekt, is een consequent doorgetrokken contract: welke JSON-velden zijn stabiel? Hoe signaleren we fouten? Hoe herkennen we een request terug zodra het via load balancer, TLS-terminatie en meerdere backendlagen is gegaan?

De volgende aanpak (inclusief Delphi-snippet) toont een robuuste lijn voor RemObjects SDK: JSON-contracten versioneren, Correlation-ID (Request-ID voor traceerbaarheid) afdwingen, Exceptions vertalen naar HTTP-status en JSON-foutobjecten en daarbij debugging en operatie niet tegen elkaar uitspelen. Daarnaast kijken we naar randgevallen die in echte omgevingen regelmatig voorkomen: threading op servers, database-toegang bij de BDE-aflossing met native binding, proxy-headers, timeouts en „vuile“ client-payloads.

Architectuurbeslissing: versiebeheer via Media Type in plaats van URL

Veel API’s versioneren via paden zoals /v1/. Dat is pragmatisch, maar in langlopende integraties (bijv. ERP/DMS/CRM-koppelingen) leidt het vaak tot URL-duplicatie, dubbele routes, dubbele tests en de vraag „Welke versie gebruiken we eigenlijk?“ in operationele handleidingen.

Een alternatief is versiebeheer via de Media Type (Content Negotiation). De client stuurt bijvoorbeeld Accept: application/vnd.company.order+json;v=2. De server leest de versie deterministisch uit en past het contract-/DTO-gedrag aan. Dat werkt in proxy- en cacheketens, mits de headers correct worden doorgegeven. Voor beheerders is het bovendien goed controleerbaar: een request is met Curl/Postman reproduceerbaar zonder dat de URL’s van elkaar verschillen.

RemObjects SDK is niet „REST-puristisch“, maar een pragmatisch service-framework. Juist daarom is de media-type-variant de moeite waard: u kunt stabiele eindpunten behouden en toch contracten verder ontwikkelen. Belangrijk is dat u de versie altijd uitwert en centraal op één plek beslist, en het resultaat in uw servicecontext overneemt.

Wanneer faalt de Accept-header-variant?

In de praktijk zijn er drie typische breekpunten die u vooraf moet adresseren:

  • Proxy-beleid: sommige reverse proxies/WAF-regels normaliseren of filteren Accept-headers. Dan valt uw API stilletjes terug op de default. Oplossing: controleer proxyregels expliciet en wijk zo nodig uit naar X-Api-Version.
  • Client-bibliotheken: sommige HTTP-clients zetten eigen Accept-headers en overschrijven waarden. Oplossing: ondersteun contractversie ook als optionele queryparameter (alleen als fallback), of parseer de Accept-header serverzijde tolerant.
  • Caching: Wanneer Response-Caching in het spel is, moet de cache variëren op basis van Accept (Vary: Accept), anders levert hij versie 1 aan versie-2-clients. Oplossing: Vary bewust zetten, of caching op API-niveau uitschakelen.
  • Bronfragment: Request-Context, Correlation-ID, Versie en Error-Mapping

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

    • Correlation-ID: Unieke ID per request, die in de response terugkeert en in logs wordt opgenomen.
    • 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 het gedrag en de 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;
    // 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.

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

    Het snippet maakt een bewuste scheiding: TApiContext is de minimale staat die u wilt doorgeven. In RemObjects SDK wordt veel afgehandeld via server-/channel-context. In heterogene projecten (bijv. extra Worker-Threads, DB-Queue, achtergrondjobs) is expliciet doorgeven vaak robuuster dan impliciete Threadlocals, omdat u daarmee gelijktijdigheid en contextwisselingen zichtbaarder maakt.

    Randvoorwaarden: De Accept-header-variant vereist 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 slechts zo goed als uw tests. Als clients libraries gebruiken die de Accept-header overschrijven, kan een API plotseling terugvallen op de standaard. Voor legacy-clients is een default-fallback zinvol, maar die moet zichtbaar zijn in monitoring (bijv. log-waarschuwing „Version defaulted“).

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

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

    Het daadwerkelijke effect ontstaat wanneer u de mechaniek consequent aan de rand van uw server toepast: eenmaal bij het binnenkomen van het request uit headers lezen, eenmaal bij het afhandelen van exceptions naar een stabiele response vertalen. Afhankelijk van de hosting (bijv. RO-HTTP-Server, IIS-Hosting, zelf beheerde Windows-/Windows- und Linux-Services) verschillen de concrete hook-punten; het principe blijft gelijk: context opbouwen, business-logic aanroepen, exceptions centraal mappen.

    In RemObjects-projecten wordt vaak per servicemethode direct gewerkt. Dat schaalt aanvankelijk goed, maar kantelt in productie: elke methode bouwt logging en foutafhandeling anders op. Een schone scheiding is een Service-Basis of een Dispatcher die standaardiseert.

    Praktische werkwijze (bewust kort en implementatiegericht)

    1. Correlation-ID uit de request-header X-Correlation-ID lezen; als deze ontbreekt, serverzijde genereren (bijv. GUID).
    2. Contract-versie uit Accept lezen (of uit X-Api-Version).
    3. Request-start loggen: methode, pad, Correlation-ID, Remote IP, duurmeting starten.
    4. Business-logic uitvoeren; DB-toegangen bij voorkeur transactioneel kapselen.
    5. Exceptions afvangen: HTTP-status bepalen, JSON-foutobject aanmaken, response-header X-Correlation-ID zetten.
    6. Request-einde loggen: status, duur, indien van toepassing foutcode.

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

    Een veelvoorkomend Delphi-randgeval: de servicemethode triggert asynchrone taken (bijv. rapportgeneratie, import, push naar een DMS). Dan is de oorspronkelijke request-thread niet langer 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 lijkt het op meer parameterlijsten, het betaalt zich uit. Als alternatief kunt u werken met een duidelijk gedefinieerd context-object dat bewust aan workers wordt doorgegeven (in plaats van globale variabelen of verborgen singletons).

    Typische kantelpunten in RemObjects-/Delphi-servers:

    • DB-verbindingen per thread: BDE-Ablosung mit nativer Anbindung-verbindingen zijn niet automatisch thread-safe deelbaar. Een connection-pool of per thread één verbinding is vaak zinvoller dan “één globale connection”.
    • Transactiegrenzen: Als u binnen een request meerdere stappen heeft die bij elkaar horen, moet de transactie binnen dezelfde logische eenheid blijven. Asynchrone verwerking mag niet per ongeluk in dezelfde transactie doorlopen.
    • Annulering: Wanneer de client afbreekt (proxy timeout, browser gesloten), draait de server vaak door. Overweeg bewust of achtergrondwerk dan nog zin heeft.

    Gegevens‑toegang en foutcodes: 409 is niet „ook een 500”

    In integratieprojecten is zorgvuldig error‑mapping meer dan cosmetica. Het bepaalt of een tegenpartij (ERP‑connector, ETL‑job, klantenportaal) correct kan reageren. Een paar praktijkgerichte uitgangspunten die zich in Delphi/RemObjects‑omgevingen hebben bewezen:

    • 400 Bad Request: Validatie, ontbrekende/ongeldige parameters, JSON niet te parsen. Belangrijk: het antwoord moet stabiel blijven, ook als de body corrupt is.
    • 401/403: Authenticatie en autorisatie scheiden. 401 betekent ‘geen/ongeldige identiteit’, 403 ‘identiteit ok, maar niet toegestaan’.
    • 404: Resource bestaat niet. Voorzichtigheid bij beveiliging: niet altijd prijsgeven of iets bestaat.
    • 409 Conflict: Functioneel conflict (bijv. versiesconflict, “status staat deze actie niet toe”, schending van een unieke sleutel als dat functioneel relevant is).
    • 422 Unprocessable Content: Wanneer syntactisch alles ok is, maar de functionele validatie faalt (niet elk team gebruikt 422, maar het is vaak duidelijker dan 400).
    • 500: Alles wat u niet eenduidig kunt classificeren. Daartoe behoren ook “DB down”, “Timeout”, “Unhandled Exception”.

    Delphi‑specifieke tip: Veel DB‑fouten komen als generieke exceptions omhoog. Het loont om in de datatoegangslaag gericht op bekende situaties te controleren en deze om te zetten naar EApiError. Belangrijk daarbij: geen SQL‑fragmenten of interne tabel‑/kolomnamen overnemen in het clientbericht. Die details horen in de logs, niet in de response.

    Debugging‑tip: reproduceerbare fouten door „Contract Snapshot”

    Ongewoon, maar in de operatie extreem nuttig: sla bij fouten (of gericht voor bepaalde Correlation‑IDs) een “snapshot” van request‑headers + request‑body op in een debug‑spoolbestand. Dit is geen permanent logging (privacy/volume), maar een gecontroleerd instrument om moeilijk reproduceerbare gevallen vanuit productiecontext na te bootsen.

    Belangrijk: een snapshot mag nooit ongefilterd auth‑headers, tokens of persoonsgegevens persisteren. In de praktijk betekent dit: redaction (maskering) en activering alleen via een feature‑flag of whitelist (bijv. alleen voor bepaalde Correlation‑IDs, korte tijdvensters).

    Schone uitvoering in de praktijk: maskeren in plaats van weglaten

    In echte integraties zijn juist de “kritische” velden vaak degene die je voor debugging nodig hebt (bijv. identificatoren). In plaats van alles standaard weg te laten is maskeren beter: tokens gedeeltelijk vervangen, e‑mail alleen de domein behouden, IBAN alleen de laatste cijfers. Zo blijft het geval reproduceerbaar zonder onnodige data op het bestandssysteem te verspreiden. Daarnaast moet de snapshot duidelijk als debug‑artefact gekenmerkt zijn en een vaste bewaartermijn hebben.

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

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

    • Remote IP: Vertrouw niet blind op X-Forwarded-For. Neem het alleen over van vertrouwde proxies en gebruik anders de directe socket-IP. In exploitatiehandleidingen moet staan welke hops „trusted“ zijn.
    • Timeouts: Als een proxy 30 seconden heeft en uw backend 2 minuten nodig heeft, creëert u Ghost-Requests. Stel timeouts consistent in langs de keten en bepaal: synchrone request of job-patroon (202 Accepted + status-endpoint).
    • Correlation-ID: Zet de Correlation-ID in response-headers zodat beheerders deze uit logs en aan de clientzijde kunnen samenvoegen. Als een gateway eigen request-IDs gebruikt: log en map beide IDs.
    • Foutteksten: In de productieomgeving geen interne details. Debugdetails alleen gecontroleerd (Stage/Feature-Flag) en bij twijfel alleen in het log.

    Plaatsing: waarom RemObjects SDK hier in het voordeel kan zijn

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

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

    De getoonde aanpak voegt daaraan de onderdelen toe die in de dagelijkse praktijk vaak ontbreken: uniforme foutobjecten, deterministische versionering en correleerbare logging. Juist bij maatwerk bedrijfssoftware met lange levenscycli bespaart u daarmee tijd bij updates en bij de integratie van externe systemen.

    Conclusie: Loont de inspanning — en waar schiet de aanpak tekort?

    De meerwaarde ontstaat wanneer uw REST-interface niet alleen „werkt“, maar duurzaam beheersbaar is: stabiele JSON-contracten, versionering zonder wildgroei in URL’s, traceerbare fouten en debugging zonder giswerk. Juist 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, lijkt media-type-versionering snel op 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 op orde brengen; anders debugt u de verkeerde laag.

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

    In de vakinhoudelijke context spelen ook Delphi REST-API en REST-Server en Remobjects Sdk Delphi een belangrijke rol, wanneer integraties, datastromen en doorontwikkeling nauwkeurig op elkaar moeten aansluiten.

    Project of moderniseringsproject met Net-Base bespreken.

    Volgende stap

    Wanneer het onderwerp een echt project wordt, zouden architectuur, bestaande omgeving en beheer in een vroeg stadium gezamenlijk moeten 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, gegevens‑toegang, portalen en uitrol worden niet als latere gevolgen uitgesteld.
    • U ziet vroeg welke weg economisch en operationeel houdbaar 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.