Net-Base Magasin

09.06.2026

REST API med RemObjects SDK: konsekvent versionering och felsökning av JSON-endpunkter (Delphi källkodsexempel)

Hur du med RemObjects SDK i Delphi bygger en REST API som inte fallerar i produktion: stabila JSON-kontrakt, versionering utan versionsspridning i URL:er, Correlation-ID genom alla lager, centraliserad felmappning, snapshot-loggning för svåra felsökningsfall samt praktiska anvisningar...

09.06.2026

Från magasinets tema till projektpraxis

Passande tjänste- och tekniksidor för inlägget

Varför „REST API med RemObjects SDK“ i praktiken ofta avgörs i gränsytorna

En REST API med RemObjects SDK avgörs sällan av en ”Hello World”-service, utan av de punkter där drift, ärvda system och integration kolliderar: versionering utan driftstopp, konsekvent felbeteende över alla endpunkter, reproducerbar felsökning i proxy-kedjor och förmågan att entydigt korrelera förfrågningar vid problem.

RemObjects SDK medför mycket infrastruktur: tjänster, meddelandeformat, serialisering, hosting (t.ex. som Windows- och Linux-services eller bakom IIS/Reverse Proxy) och definierade platser för central felhantering. Vad som ofta saknas i etablerade affärssystemlandskap är dock ett konsekvent genomfört kontrakt: Vilka JSON-fält är stabila? Hur signalerar vi fel? Hur identifierar vi en förfrågan igen när den gått genom load balancer, TLS-terminering och flera backend-skikt?

Följande angreppssätt (inklusive Delphi-kodexempel) visar en robust linje för RemObjects SDK: versionera JSON-kontrakt, kräva Correlation-ID (Request-ID för spårning), översätta Exceptions till HTTP-status och JSON-felobjekt och samtidigt inte ställa felsökning mot drift. Dessutom tar vi upp kantfall som regelbundet uppträder i verkliga miljöer: trådning på servern, databasåtkomster i samband med BDE-ersättning med native-anslutning, proxy-headers, timeouts och ”smutsiga” klientpayloads.

Arkitekturval: versionering via mediatyp istället för URL

Många API:er versionerar via sökvägar som /v1/. Det är pragmatiskt, men i långvariga integrationer (t.ex. ERP/DMS/CRM-anslutningar) leder det ofta till duplicerade URL:er, dubbla rutter, dubbla tester och frågan ”Vilken version använder vi egentligen?” i driftshandböckerna.

Ett alternativ är versionering via mediatyp (Content Negotiation). Klienten skickar t.ex. Accept: application/vnd.company.order+json;v=2. Servern läser deterministiskt ut versionen och anpassar kontrakt/DTO-beteende. Det fungerar i proxy- och cachekedjor om headern vidarebefordras korrekt. För administratörer är det dessutom lätt att verifiera: en förfrågan kan reproduceras med Curl/Postman utan att URL:erna skiljer sig åt.

RemObjects SDK är inte „REST-puristiskt“, utan ett pragmatiskt service-ramverk. Just därför är mediatyp-varianten värdefull: ni kan behålla stabila endpunkter och samtidigt vidareutveckla kontrakt. Viktigt är att ni alltid utvärderar versionen, beslutar centralt på en plats och för över resultatet till er servicekontext.

När brister Accept-header-varianten?

I praktiken finns det tre typiska brytpunkter som bör adresseras i förväg:

  • Proxy-Policies: Vissa reverse-proxies/WAF-regler normaliserar eller filtrerar Accept-headern. Då faller er API tyst tillbaka till standard. Lösning: kontrollera proxy-regler uttryckligen, vid behov falla tillbaka på X-Api-Version.
  • Client-Libraries: Vissa HTTP-klienter sätter egna Accept-header och överskriver värden. Lösning: stöd kontraktsversion även som en valfri query-parameter (endast som fallback), eller tolka Accept-headern tolerant på serversidan.
  • Caching: Om Response-Caching är aktivt måste cachen variera efter Accept (Vary: Accept), annars levererar den version 1 till version 2-klienter. Lösning: ange Vary medvetet, eller inaktivera caching på API-nivå.
  • Källkodsexempel: Request-Context, Correlation-ID, Version och Error-Mapping

    Koden är medvetet uppdelad så att den kan integreras i befintliga RemObjects-serverprojekt: ett litet Context-lager, en parser för API-versionen (från Accept), en Correlation-ID-mekanism och ett centralt Exception-Mapping. Begrepp:

    • Correlation-ID: En unik ID per begäran som återkommer i svaret och refereras i loggarna.
    • Exception-Mapping: Översättning av interna Delphi-exceptions till stabila felobjekt som klienten kan hantera (inkl. HTTP-status).
    • Contract-Version: Version av JSON-kontraktet som styr beteende och fält.
    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.

    Syfte: Stabil Requestkontext istället för „irgendwo im Threadlocal“

    Kodsnippeten skiljer medvetet åt: TApiContext är det minimala tillståndet du vill föra vidare. I RemObjects SDK går mycket via server-/channel-kontext. I heterogena projekt (t.ex. extra worker-trådar, DB-kö, bakgrundsjobb) är explicit vidarebefordran ofta mer robust än implicita threadlocals, eftersom det gör samtidighet och kontextbyten mer synliga.

    Förutsättningar: Accept-header-varianten förutsätter att din reverse proxy (nginx, IIS ARR, Traefik) vidarebefordrar headern utan förändring. I vissa miljöer filtreras eller aggregeras „ovanliga“ Accept-header.

    Fallgropar: Versionering via Accept är bara så bra som dina tester. Om klienter använder bibliotek som skriver över Accept kan en API plötsligt falla tillbaka till default. För legacy-klienter är en default-fallback rimlig, men den måste vara synlig i övervakningen (t.ex. loggvarning „Version defaulted“).

    Alternativ: Om du föredrar att göra versionering via X-Api-Version: parsern är identisk, bara källan är en annan header. Ur gateways perspektiv är det ibland enklare att kontrollera så.

    Integration i RemObjects SDK: Correlation-ID och Exception-Mapping vid serviceingång

    Den verkliga effekten uppstår när du tillämpar mekaniken konsekvent i kanten av din server: en gång vid request-entré läsa från headers, en gång vid exception-utgång översätta till en stabil response. Beroende på hosting (t.ex. RO-HTTP-Server, IIS-hosting, självdriven Windows-/Windows- och Linux-Services) varierar de konkreta hook-punkterna; principen är densamma: bygg kontext, anropa affärslogik, mappa exceptions centralt.

    I RemObjects-projekt arbetar man ofta direkt per servicemetod. Det skalar bra initialt, men brister i drift: varje metod bygger logging och felhantering på olika sätt. Ett tydligt snitt är en service-bas eller en dispatcher som standardiserar.

    Praktisk process (medvetet kort och implementeringsnära)

    1. Läs Correlation-ID från request-headern X-Correlation-ID; om den saknas, skapa den på serversidan (t.ex. GUID).
    2. Läs kontraktsversion från Accept (eller från X-Api-Version).
    3. Logga begärans start: metod, sökväg, Correlation-ID, remote IP, starta tidsmätning.
    4. Utför affärslogik; kapsla databasåtkomst så transaktionellt som möjligt.
    5. Fånga exceptions: bestäm HTTP-status, skapa JSON-felobjekt, sätt response-headern X-Correlation-ID.
    6. Logga begärans slut: status, varaktighet, eventuellt felkod.

    Trådning i servern: Varför Correlation-ID utan kontextdisciplin blir värdelöst

    Ett vanligt Delphi-kantfall: servicemetoden initierar asynkront arbete (t.ex. rapportgenerering, import, push till ett DMS). Då är inte längre den ursprungliga request-tråden den som skriver loggrader senare. Om Correlation-ID endast är känt „i början“ faller spårbarheten isär.

    Pragmatisk regel: Allt som inte strikt stannar i request-tråden får kontexten explicit överlämnad. Även om det ger fler parameterlistor betalar det sig. Alternativt kan man arbeta med ett tydligt definierat kontextobjekt som medvetet skickas till workers (istället för globala variabler eller dolda singletons).

    Typiska brytpunkter i RemObjects-/Delphi-servrar:

    • DB-anslutningar per tråd: BDE-Ablosung mit nativer Anbindung-anslutningar är inte automatiskt trådsäkra att dela. En Connection-Pool eller en anslutning per tråd är ofta mer ändamålsenligt än „en global Connection“.
    • Transaktionsgränser: Om du inom en request har flera steg som hör ihop måste transaktionen förbli inom samma logiska enhet. Asynkront arbete får inte „av misstag“ fortsätta i samma transaktion.
    • Avbrott: Om klienten avbryter (Proxy-timeout, webbläsare stängd) fortsätter servern ofta. Överväg medvetet om bakgrundsarbete då fortfarande är meningsfullt.

    Åtkomst till data och felkoder: 409 är inte „också en 500“

    I integrationsprojekt är korrekt felmappning mer än kosmetik. Det avgör om en motpart (ERP-Connector, ETL-Jobb, Kundenportal) kan reagera korrekt. Några praktiska riktlinjer som visat sig fungera i Delphi/RemObjects-miljöer:

    • 400 Bad Request: Validering, saknade/ogiltiga parametrar, JSON går inte att parsa. Viktigt: svaret bör vara stabilt även om kroppen är korrupt.
    • 401/403: Separera autentisering och behörighet. 401 betyder „ingen/ogiltig identitet“, 403 „identitet ok, men förbjudet“.
    • 404: Resursen finns inte. Var försiktig med säkerhet: avslöja inte alltid om något existerar.
    • 409 Conflict: Affärsrelaterad konflikt (t.ex. versionskonflikt, „status tillåter inte denna åtgärd“, unik nyckelöverträdelse när den är affärsrelevant).
    • 422 Unprocessable Content: När syntaxen är ok men affärsvalidering misslyckas (inte alla team använder 422, men det är ofta tydligare än 400).
    • 500: Allt som ni inte kan klassificera på ett tydligt sätt. Det inkluderar också „DB down“, „Timeout“, „Unhandled Exception“.

    Delphi-specifikt knep: Många DB-fel kommer upp som generiska Exceptions. Det är värt att i dataåtkomstlagret specifikt kontrollera kända situationer och konvertera dem till EApiError. Viktigt: inför inga SQL-fragment eller interna tabell-/kolumnnamn i klientmeddelandet. Dessa detaljer hör hemma i loggen, inte i svaret.

    Debuggingknep: reproducerbara fel genom „Contract Snapshot“

    Ovanligt, men i drift extremt hjälpsamt: Spara vid fel (eller selektivt för vissa Correlation-IDs) en „Snapshot“ av Request-Headern + Request-Body i en debug-spoolfil. Det är inte permanent loggning (dataskydd/volym), utan ett kontrollerat verktyg för att återskapa svårreproducerade fall nära produktion.

    Viktigt: En Snapshot får aldrig persistiera ofiltrerade Auth-Header, Tokens eller personuppgifter. I praktiken betyder det: Redaction (maskering) och aktivering endast via Feature-Flag eller Whitelist (t.ex. bara för specifika Correlation-IDs, korta tidsfönster).

    Korrekt genomförande i praktiken: Maskera istället för att utelämna

    I riktiga integrationer är just de „kritiska“ fälten ofta de man behöver för debugging (t.ex. identifierare). Istället för generell utelämning är maskering bättre: ersätt delar av Token, behåll endast domänen i e-postadresser, IBAN endast de sista siffrorna. På så sätt förblir fallet reproducerbart utan att sprida onödiga data i filsystemet. Dessutom bör Snapshot tydligt märkas som debug-artefakt och ha en definierad lagringstid.

    Säkerhet och drift: Header-vidarebefordran, proxykedjor och timeouter

    Ett REST-API slutar sällan direkt vid klienten. Typiskt är kedjor av reverse proxy, TLS-terminering, WAF eller API-gateway. Det ger praktiska konsekvenser:

    • Remote-IP: Lita inte blint på X-Forwarded-For. Acceptera det endast från betrodda proxies och använd annars den direkta socket-IP:n. I driftmanualer bör det framgå vilka hopp som är „trusted“.
    • Timeouter: Om proxyn har 30 sekunder men ert backend kräver 2 minuter skapar ni ghost-requests. Sätt timeouter konsekvent längs kedjan och avgör: synkront anrop eller jobbmönster (202 Accepted + status-endpoint).
    • Correlation-ID: Sätt Correlation-ID i response-headern så att administratörer kan korrelera det mellan loggar och klientsidan. Om ett gateway använder egna request-ID:n: logga och avbilda båda ID:erna.
    • Felmeddelanden: I produktionsdrift inga interna detaljer. Debugdetaljer endast kontrollerat (Stage/Feature-Flag) och i tveksamma fall endast i loggen.

    Bedömning: Varför RemObjects SDK kan vara en fördel här

    I Delphi-ekosystem byggs REST-servrar ofta med lättare ramverk (t.ex. minimalistiska HTTP-router). RemObjects SDK visar sin styrka när ni redan har eller behöver en flerskiktsarkitektur:

    • Tydliga servicegränser: Servicemetoder är explicita, kontrakt går att versionera.
    • Transports och serialisering: Ni kan använda JSON men också andra meddelandeformat (beroende på setup), utan att blanda ihop domänlogiken.
    • Drift: Hostingsalternativ och integration i befintliga Windows- och Linux-services är planbara, inklusive kontrollerade utrullningar.

    Den visade ansatsen kompletterar detta med de delar som ofta saknas i vardagen: enhetliga felobjekt, deterministisk versionering och korrelerbar loggning. Särskilt för skräddarsydd företagsmjukvara med lång livscykel sparar ni tid vid uppdateringar och vid integration med externa system.

    Slutsats: Är mödan värd – och var brister tillvägagångssättet?

    Värdet uppstår när ert REST-gränssnitt inte bara „fungerar“, utan är varaktigt driftbart: stabila JSON-kontrakt, versionering utan URL-vildvuxenhet, spårbara fel och debugging utan gissningslek. Precis där är tillvägagångssättet med Context, Correlation-ID och centralt exception-mapping i RemObjects SDK starkt.

    Användningsbegränsningar: Om ni bara har en enstaka, kortlivad endpoint utan integrationspartners framstår Media-Type-versionering snabbt som overengineering. Även snapshot-logging är bara meningsfullt om ni disciplinerad implementerar redaction och aktivering. Och: Om er proxy-stack „optimerar“ eller tar bort headers måste ni först rätta till infrastrukturen, annars debuggar ni fel lager.

    Om ni ska modernisera en befintlig Delphi-servermiljö eller behöva integrera en processnära mjukvarulösning ordentligt i ERP/DMS/CRM, är just dessa mekanismer ofta skillnaden mellan „fungerar i test“ och „fungerar i drift“.

    I det verksamhetsmässiga sammanhanget spelar också Delphi REST-API och REST-Server och Remobjects Sdk Delphi en viktig roll när integrationer, dataflöden och vidareutveckling måste samspela på ett ordnat och förutsägbart sätt.

    Diskutera projekt eller moderniseringsinitiativ med Net-Base.

    nästa steg

    När ett ämne blir ett verkligt projekt bör arkitektur, befintligt bestånd och drift tidigt ses över gemensamt.

    Vi stöder inte bara vid enstaka frågor, utan även när kodsfragment, legacy-frågor eller portalidéer ska utvecklas till ett robust företagsprojekt.

    • Nuläge, målbild och tekniska risker bedöms tillsammans.
    • REST, dataåtkomst, portaler och utrullning skjuts inte upp som sena följder.
    • Ni ser tidigt vilken väg som är ekonomiskt och driftmässigt hållbar.

    Dela inlägg

    Dela det här inlägget direkt

    LinkedIn, X, XING, Facebook, WhatsApp och e-post är omedelbart tillgängliga. För Instagram förbereder vi länken och en kort text direkt.

    E-post

    Instagram öppnas i en ny flik. Länken och korttexten kopieras till urklipp först.