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“ ofta avgörs i praktiken i marginalfall

En REST API med RemObjects SDK står och faller sällan vid en „Hello World“-tjänst, utan vid de ställen där drift, legacy och integration kolliderar: versionering utan driftstopp, konsekvent felbeteende över alla endpunkter, reproducerbar felsökning genom proxy-kedjor och förmågan att entydigt korrelera requests vid problem.

RemObjects SDK medför mycket infrastruktur: tjänster, meddelandeformat, serialisering, hosting (t.ex. som Windows- och Linux-tjänster eller bakom IIS/Reverse Proxy) och definierade punkter för central felhantering. Vad som ofta saknas i etablerade affärsprogrammiljöer är dock ett konsekvent genomdrivet kontrakt: Vilka JSON-fält är stabila? Hur signalerar vi fel? Hur känner vi igen en request när den passerat 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 behandlar vi kantfall som i verkliga miljöer ofta uppstår: trådning på servern, databasåtkomst vid BDE-utbyte med native-anslutning, proxy-headers, timeouter och „smutsiga“ klientpayloads.

Arkitekturbeslut: 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ånglivade integrationer (t.ex. ERP/DMS/CRM-anslutningar) leder det ofta till duplicering av URL:er, dubbla rutter, dubbla tester och frågan „vilken version använder vi egentligen?“ i driftshandböckerna.

Ett alternativ är versionering via mediatyp (innehållsförhandling). Klienten skickar t.ex. Accept: application/vnd.company.order+json;v=2. Servern läser versionen deterministiskt och anpassar kontrakt/DTO-beteende. Det fungerar i proxy- och cache-kedjor när headern vidarebefordras korrekt. För administratörer är det dessutom lätt att kontrollera: en request 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ärd att överväga: ni kan behålla stabila endpunkter och ändå vidareutveckla kontrakt. Viktigt är att ni alltid utvärderar versionen, bestämmer centralt och för över resultatet till er servicekontext.

När brister Accept-header-varianten?

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

  • Proxy-policyer: Vissa reverse-proxies/WAF-regler normaliserar eller filtrerar Accept-headern. Då faller API:t tyst tillbaka till default. Lösning: granska proxy-regler uttryckligen, eventuellt falla back till X-Api-Version.
  • Klientbibliotek: Vissa HTTP-klienter sätter egna Accept-headers och skriver över värden. Lösning: stödja kontraktsversion även som en valfri query-parameter (endast som fallback), eller tolerera och parsaa Accept-headern server-sidigt.
  • Caching: Om respons-caching används måste cachen variera efter Accept (Vary: Accept), annars levererar den version 1 till version 2-klienter. Lösning: Vary medvetet sättas, eller inaktivera caching på API-nivå.

Källkodsexempel: Request-Context, Correlation-ID, Version och Error-Mapping

Koden är medvetet utformad 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 en central exception-mappning. Begrepp:

  • Correlation-ID: Unik identifierare per förfrågan som återfinns i svaret och refereras i loggar.
  • Exception-Mapping: Översättning av interna Delphi-exceptions till stabila, klientbearbetbara felobjekt (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 request-kontext istället för „irgendwo im Threadlocal“

Snippetet separerar medvetet: TApiContext är det minimala tillståndet som du vill föra vidare. I RemObjects SDK går mycket via server-/channel-kontext. I heterogena projekt (t.ex. ytterligare worker-trådar, DB-kö, bakgrundsjobb) är explicita vidarebefordringar ofta mer robusta än implicita threadlocals, eftersom du därigenom 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 oförändrad. I vissa miljöer filtreras eller sammanfogas „ovanliga“ Accept-headers.

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 standard. För legacy-klienter är en default-fallback rimlig, men den måste vara synlig i övervakningen (t.ex. loggvarning „Version defaulted“).

Varianter: Om du föredrar versionering via X-Api-Version: parsern är identisk, bara källan är en annan header. Ur gateway-synpunkt är det ibland enklare att kontrollera.

Integration i RemObjects SDK: Correlation-ID och Exception-Mapping vid serviceinträde

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

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

Praktiskt förlopp (medvetet kort och implementationsnära)

  1. Läs Correlation-ID från request-header 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 request-start: metod, sökväg, Correlation-ID, remote IP, starta tidsmätning.
  4. Kör affärslogik; kapsla databasåtkomster transaktionellt där det är möjligt.
  5. Fånga exceptions: bestäm HTTP-status, skapa JSON-felobjekt, sätt response-header X-Correlation-ID.
  6. Logga request-slut: status, varaktighet, vid behov felkod.

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

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

Pragmatisk regel: Allt som inte strikt stannar i request-tråden får kontexten explicit överlämnad. Även om det ser ut som fler parameterlistor lönar det sig. Alternativt kan man arbeta med ett tydligt definierat kontextobjekt som medvetet skickas till workern (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äkert delbara. En anslutningspool eller en anslutning per tråd är ofta mer vettigt ä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.
  • Avbrytning: Om klienten avbryter (Proxy timeout, Browser closed) fortsätter servern ofta. Överväg noga om bakgrundsarbete då fortfarande är meningsfullt.

Dataåtkomst 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, kundportal) kan reagera korrekt. Några praktiska riktlinjer som visat sig vara användbara i Delphi/RemObjects-miljöer:

  • 400 Bad Request: Validering, saknade/ogiltiga parametrar, JSON ej parsbar. Viktigt: Svaret bör vara stabilt även om body är korrupt.
  • 401/403: Skilj autentisering från auktorisation. 401 betyder „ingen/ogiltig identitet“, 403 „identiteten ok men förbjuden“.
  • 404: Resurs finns inte. Var försiktig ur säkerhetssynpunkt: avslöja inte alltid om något finns.
  • 409 Conflict: Domänkonflikt (t.ex. versionskonflikt, „status tillåter inte denna åtgärd“, unik nyckelöverträdelse när den är relevant för domänen).
  • 422 Unprocessable Content: När syntaktiskt allt är ok men domänvalidering misslyckas (inte alla team använder 422, men det är ofta tydligare än 400).
  • 500: Allt som inte kan klassificeras tydligt. Det inkluderar också „DB down“, „Timeout“, „Unhandled Exception“.

Delphi-specifikt knep: Många DB-fel kommer upp som generiska exceptions. Det lönar sig att i dataåtkomstlagret målmedvetet kontrollera kända situationer och överföra dem till EApiError. Viktigt: Ta inte med SQL-fragment eller interna tabell-/kolumnnamn i klientmeddelandet. Dessa detaljer hör hemma i loggen, inte i responsen.

Debugging-knep: reproducerbara fel med „Contract Snapshot“

Ovanligt, men i drift extremt användbart: Spara vid fel (eller riktat för vissa Correlation-IDs) en „snapshot“ av Request-headers + Request-body i en debug-spoolfil. Detta är ingen kontinuerlig loggning (Datenschutz/Volym), utan ett kontrollerat verktyg för att återskapa svårreproducerade fall nära produktion.

Viktigt: En snapshot får aldrig persistiera ofiltrerade auth-headers, tokens eller personuppgifter. I praktiken innebär det: Redaction (maskering) och aktivering endast via feature-flag eller whitelist (t.ex. endast för specifika Correlation-IDs, korta tidsfönster).

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

I verkliga integrationer är det ofta de „kritiska“ fälten som krävs 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 ärendet reproducerbart utan att sprida onödiga data i filsystemet. Dessutom bör snapshoten tydligt märkas som ett debug-artefakt och ha en definierad lagringstid.

Säkerhet och drift: vidarebefordran av headers, proxy-kedjor och timeouts

En REST API slutar sällan direkt vid klienten. Typiskt är kedjor av reverse proxy, TLS-terminering, WAF eller API-gateway. Därav följer praktiska punkter:

  • Fjärr-IP: Lita inte blint på X-Forwarded-For. Acceptera endast värden från betrodda proxies och använd annars den direkta socket-IP:n. I driftshandböcker bör det anges vilka hopp som är betrodda.
  • Timeouts: Om proxyn har 30 sekunder men ert backend behöver 2 minuter, skapar ni ghost-requests. Sätt timeouts konsekvent längs hela kedjan och fatta ett beslut: synkront anrop eller jobbmönster (202 Accepted + status-endpunkt).
  • Correlation-ID: Sätt Correlation-ID i svarshuvuden så att administratörer kan korrelera det mellan loggar och klientsidan. Om ett gateway använder egna request-ID:n: logga och mappa båda ID:erna.
  • Feltexter: I produktionsdrift inga interna detaljer i svaren. 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-Server 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:

  • Klara servicegränser: servicemetoder är explicita, kontrakt kan versioneras.
  • Transports och serialisering: Ni kan tala JSON, men även andra meddelandeformat (beroende på setup), utan att förorena domänlogiken.
  • Drift: Hosting-alternativ och integration i befintliga Windows- och Linux-Services är planbara, inklusive ordnade 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ånga livscykler sparar ni därmed tid vid uppdateringar och vid integration av externa system.

Slutsats: Är insatsen värd det — och när blir ansatsen olämplig?

Mervärdet uppstår när ert REST-gränssnitt inte bara „fungerar“, utan är driftsäkert på lång sikt: stabila JSON-kontrakt, versionering utan URL-vildväxt, spårbara fel och felsökning utan gissningslek. Just där är ansatsen med Context, Correlation-ID och centralt exception-mapping i RemObjects SDK stark.

Användningsgränser: Om ni bara har en enda, kortlivad endpoint utan integrationspartners framstår media-type-versionering snabbt som overengineering. Även snapshot-loggning är bara meningsfull om ni disciplinerat implementerar redaction och aktivering. Och: om er proxy-stack optimerar eller tar bort headers, måste ni först åtgärda infrastrukturen, annars felsöker ni fel lager.

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

I det verksamhetsmässiga sammanhanget spelar även 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 strukturerat och tillförlitligt sätt.

Diskutera projekt eller moderniseringsprojekt med Net-Base.

Nästa steg

När ett ämne blir ett verkligt projekt bör arkitektur, befintliga system och drift behandlas gemensamt redan i ett tidigt skede.

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 driftsmässigt bärkraftig.

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.