Net-Base Magasin

09.06.2026

REST API med RemObjects SDK: JSON-endepunkter — struktureret versionsstyring og fejlsøgning (Delphi kodeeksempel)

Hvordan du med RemObjects SDK i Delphi opbygger en REST API, der ikke bryder sammen i drift: stabile JSON-kontrakter, versionering uden vildtvoksende URL-strukturer, Correlation-ID gennem alle lag, centraliseret Error-Mapping, Snapshot-logging til krævende debug-tilfælde samt praksisnære anvisninger...

09.06.2026

Fra magasinets tema til projektpraksis

Passende service- og tekniske sider til artiklen

Hvorfor „REST API med RemObjects SDK“ i praksis ofte afgør ved grænsefladerne

En REST API med RemObjects SDK lykkes sjældent eller fejler på „Hello World“-servicen, men der hvor drift, legacy og integration støder sammen: versionering uden stilstand, konsistent fejladfærd på tværs af alle endepunkter, reproducerbar fejlsøgning i proxy-kæder og evnen til entydigt at korrelere Requests i problemtilfælde.

RemObjects SDK leverer meget infrastruktur: services, meddelelsesformater, serialisering, hosting (f.eks. som Windows- og Linux-Services eller bag IIS/Reverse Proxy) og definerede steder til central fejlbehandling. Hvad der i etablerede business-software-landskaber ofte mangler, er en konsekvent gennemført kontrakt: Hvilke JSON-felter er stabile? Hvordan signalerer vi fejl? Hvordan genkender vi en Request, når den har passeret load balancer, TLS-termination og flere backend-lag?

Følgende tilgang (inklusive Delphi-snippets) viser en robust linje for RemObjects SDK: Versionere JSON-kontrakter, håndhæve Correlation-ID (Request-ID til sporing), oversætte Exceptions til HTTP-status og JSON-fejlobjekter og samtidig undgå at sætte fejlfinding og drift op imod hinanden. Derudover ser vi på kanttilfælde, som i rigtige miljøer optræder regelmæssigt: trådning på serveren, databaseadgange med BDE-Ablösung med native tilslutning, proxy-headere, timeouts og „beskidte“ client-payloads.

Arkitekturbeslutning: Versionering via medietype i stedet for URL

Mange APIs versionerer via stier som /v1/. Det er pragmatisk, men i længerevarende integrationer (f.eks. ERP/DMS/CRM-tilslutninger) fører det ofte til duplikering af URLs, dobbelte ruter, dobbelt testarbejde og spørgsmålet „Hvilken version bruger vi egentlig?“ i driftshåndbøgerne.

Et alternativ er versionering via medietype (Content Negotiation). Klienten sender f.eks. Accept: application/vnd.company.order+json;v=2. Serveren aflæser versionen deterministisk og tilpasser kontrakt/DTO-adfærd derefter. Det virker i proxy- og cache-kæder, såfremt headerne videreføres korrekt. For administratorer er det også let at verificere: En request kan reproduceres med Curl/Postman uden at ændre URLs.

RemObjects SDK er ikke „REST-puristisch“, men et pragmatisk service-framework. Netop derfor er medietype-tilgangen værdifuld: I kan bevare stabile endepunkter og alligevel videreudvikle kontrakter. Vigtigt er, at I altid evaluerer versionen, træffer beslutningen centralt og adopterer resultatet i jeres service-kontekst.

Hvornår svigter Accept-header-varianten?

I praksis er der tre typiske brudflader, som bør adresseres på forhånd:

  • Proxy-politikker: Nogle Reverse Proxies/WAF-regler normaliserer eller filtrerer Accept-headeren. Så falder jeres API stille tilbage til default. Løsning: Kontroller proxy-regler eksplicit, eventuelt benyt X-Api-Version som fallback.
  • Klientbiblioteker: Nogle HTTP-klienter sætter deres egne Accept-headere og overskriver værdier. Løsning: Understøt kontrakt-version også som en valgfri query-parameter (kun som fallback), eller parse Accept-headeren tolerant på serversiden.
  • Caching: Hvis Response-Caching er aktivt, skal cachen variere baseret på Accept (Vary: Accept), ellers vil den levere version 1 til version-2-klienter. Løsning: sæt Vary eksplicit, eller deaktiver caching på API-niveau.

Kildeudsnit: Request-Context, Correlation-ID, Version og Error-Mapping

Koden er bevidst designet, så den kan integreres i eksisterende RemObjects-serverprojekter: et lille context-lag, en parser til API-versionen (ud fra Accept), en Correlation-ID-mekanisme og et centralt exception-mapping. Begreber:

  • Correlation-ID: Entydig ID per Request, der genfindes i responsen og kan henvises til i logs.
  • Exception-Mapping: Oversættelse af interne Delphi-Exceptions til stabile, klient-behandlelige fejlobjekter (inkl. HTTP-status).
  • Contract-Version: Version af JSON-kontrakten, der styrer adfærd 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.

Formål: Stabil Request-kontekst i stedet for „et eller andet i Threadlocal“

Snippetset adskiller bevidst: TApiContext er den minimale tilstand, som De vil videreføre. I RemObjects SDK foregår meget via server-/channel-kontekst. I heterogene projekter (fx ekstra Worker-Threads, DB-Queue, baggrundsjob) er eksplicit videreføring dog ofte mere robust end implicitte Threadlocals, fordi det gør samtidighed og kontekstskift mere synlige.

Randbetingelser: Accept-Header-varianten forudsætter, at Deres reverse proxy (nginx, IIS ARR, Traefik) videresender headeren uændret. I nogle miljøer bliver „ukonventionelle“ Accept-headers filtreret eller sammenfattet.

Stolperfarer: Versionering via Accept er kun så god som Deres tests. Hvis klienter bruger biblioteker, der overskriver Accept, kan en API pludselig falde tilbage til standard. For legacy-klienter er en default-fallback fornuftig, men den må være synlig i overvågning (fx log-advarsel „Version defaulted“).

Varianter: Hvis De foretrækker at versionere via X-Api-Version: Parseren er identisk, kun kilden er en anden header. Set fra gateways‘ synspunkt er det nogle gange lettere at kontrollere.

Integration i RemObjects SDK: Correlation-ID og Exception-Mapping ved serviceindgang

Den egentlige effekt opnås, når mekanikken anvendes konsekvent i kanten af serveren: læs én gang ved request-indgang fra headers, oversæt én gang ved exception-udgang til en stabil response. Afhængigt af hosting (fx RO-HTTP-Server, IIS-hosting, selvbetjente Windows-/Windows- og Linux-services) varierer de konkrete hook-punkter; princippet er det samme: bygg context, kald business-logic, map exceptions centralt.

I RemObjects-projekter arbejdes der ofte direkte per service-metode. Det skalerer godt i starten, men bryder sammen i drift: Hver metode bygger logging og fejlhåndtering forskelligt. Et rent snit er en service-basis eller en dispatcher, der standardiserer.

Praktisk forløb (bevidst kort og implementeringsnært)

  1. Læs Correlation-ID fra request-header X-Correlation-ID; hvis den mangler, genereres den server-side (fx GUID).
  2. Læs kontrakt-version fra Accept (eller fra X-Api-Version).
  3. Log request-start: metode, sti, Correlation-ID, remote IP, start varighedsmåling.
  4. Kør business-logic; kapsl DB-adgang så vidt muligt transaktionelt.
  5. Opfang exceptions: bestem HTTP-status, opbyg JSON-fejlobjekt, sæt response-header X-Correlation-ID.
  6. Log request-slut: status, varighed, evt. fejlkode.

Threading på serveren: Hvorfor Correlation-ID uden kontekst-disciplin er værdiløs

Et hyppigt Delphi-randtilfælde: Service-metoden udløser asynkront arbejde (fx rapportgenerering, import, push til et DMS). Så er den oprindelige request-thread ikke længere den, der senere skriver loglinjer. Hvis Correlation-ID kun er kendt „i starten“, opløses sporbarheden.

Pragmatisk regel: Alt, der ikke forbliver strikt i request-threaden, får konteksten eksplicit overleveret. Selv om det giver flere parameterrækker, betaler det sig. Alternativt kan man arbejde med et klart defineret kontekstobjekt, der bevidst gives til workere (i stedet for globale variabler eller skjulte singletons).

Typiske kippekilder i RemObjects-/Delphi-servere:

  • DB-forbindelser pr. tråd: BDE-Ablosung mit nativer Anbindung-forbindelser er ikke automatisk trådsikre. En forbindelsespool eller en forbindelse pr. tråd er ofte mere fornuftigt end „en global Connection“.
  • Transaktionsgrænser: Hvis du inden for en request har flere trin, der hører sammen, skal transaktionen forblive i samme logiske enhed. Asynkront arbejde må ikke „utilsigtet“ fortsætte i den samme transaktion.
  • Afbrydelse: Hvis klienten afbryder (Proxy timeout, Browser closed), fortsætter serveren ofte. Overvej bevidst, om baggrundsarbejde i så fald stadig giver mening.

Dataadgang og fejlkoder: 409 er ikke „også en 500“

I integrationsprojekter er en ordentlig fejlkortlægning mere end kosmetik. Den afgør, om en modpart (ERP-Connector, ETL-Job, Kundenportal) kan reagere korrekt. Et par praksisnære pejlemærker, som har vist sig nyttige i Delphi/RemObjects-miljøer:

  • 400 Bad Request: Validering, manglende/ugyldige parametre, JSON ikke parsebar. Vigtigt: Svaret skal være stabilt, selv hvis body er beskadiget.
  • 401/403: Adskil autentificering og autorisation. 401 betyder „ingen/ugyldig identitet“, 403 „identitet ok, men forbudt“.
  • 404: Ressource findes ikke. Vær forsigtig af sikkerhedshensyn: Afslør ikke altid, om noget eksisterer.
  • 409 Conflict: Faglig konflikt (f.eks. versionskonflikt, „Status erlaubt diese Aktion nicht“, entydig nøglekonflikt, når det er fagligt relevant).
  • 422 Unprocessable Content: Når syntaksen er ok, men faglig validering fejler (ikke alle teams bruger 422, men det er ofte klarere end 400).
  • 500: Alt, hvad du ikke kan klassificere klart. Dertil hører også „DB down“, „Timeout“, „Unhandled Exception“.

Delphi-specifikt trick: Mange DB-fejl dukker op som generiske Exceptions. Det kan betale sig i dataadgangslaget målrettet at tjekke for kendte situationer og overføre dem til EApiError. Vigtigt i den forbindelse: Overfør ikke SQL-fragmenter eller interne tabel-/kolonnenavne til klientbeskeden. Disse detaljer hører i loggen, ikke i responsen.

Debugging-trick: reproducerbare fejl gennem „Contract Snapshot“

Usædvanligt, men i drift ekstremt nyttigt: Gem ved fejl (eller målrettet for bestemte Correlation-IDs) et „Snapshot“ af Request-Headern + Request-Body i en debug-spool-fil. Det er ikke permanent logging (databeskyttelse/volumen), men et kontrolleret værktøj til at reproducere svært reproducerbare tilfælde tæt på produktion.

Vigtigt: Et Snapshot må aldrig persistere ufiltrerede Auth-Header, Tokens eller personoplysninger. I praksis betyder det: Redaction (Maskierung) og aktivering kun via feature-flag eller whitelist (f.eks. kun for bestemte Correlation-IDs, korte tidsvinduer).

Ren implementering i praksis: Maskering i stedet for udeladelse

I reelle integrationer er netop de „kritiske“ felter ofte dem, man ville bruge til debugging (f.eks. identifikatorer). I stedet for generel udeladelse er maskering bedre: erstat dele af tokens, behold kun domænet i e-mail, IBAN kun de sidste cifre. Dermed forbliver sagen reproducerbar uden at sprede unødvendige data i filsystemet. Derudover bør Snapshot klart være markeret som et debug-artefakt og have en defineret opbevaringsperiode.

Sikkerhed og drift: videresendelse af headers, proxy-kæder og Timeouts

En REST API ender sjældent direkte ved klienten. Typisk er der kæder af reverse proxy, TLS-termination, WAF eller API-gateway. Det giver følgende praktiske punkter:

  • Remote IP: Stol ikke blindt på X-Forwarded-For. Acceptér kun fra betroede proxies og ellers brug den direkte socket-IP. I driftshåndbøger bør det stå, hvilke hops der er „trusted“.
  • Timeouts: Hvis proxien har 30 sekunder, men jeres backend kræver 2 minutter, skaber I ghost-requests. Sæt timeouts konsekvent langs kæden og beslut: synkront request eller job-pattern (202 Accepted + status-endpoint).
  • Correlation-ID: Sæt Correlation-ID i response-headers, så admins kan korrelere den mellem logs og klienten. Hvis et gateway bruger egne request-IDs: log begge IDs og kortlæg dem.
  • Fejltekster: Ingen interne detaljer i produktion. Debug-detaljer kun kontrolleret (Stage/Feature-Flag) og i tvivl kun i loggen.

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

I Delphi-økosystemer bygges REST-Server ofte med lettere frameworks (f.eks. minimalistiske HTTP-routere). RemObjects SDK udspiller sin styrke, når I allerede har eller har brug for en flerlagsarkitektur:

  • Klare servicegrænser: Servicemetoder er eksplicitte, Contracts kan versioneres.
  • Transporter og serialisering: I kan tale JSON, men også andre message-formater (afhængigt af setup), uden at forurene forretningslogikken.
  • Drift: Hostingmuligheder og integration i eksisterende Windows- og Linux-Services kan planlægges, inklusive kontrollerede rollouts.

Den viste tilgang supplerer dette med de dele, der ofte mangler i dagligdagen: ensartede fejlobjekter, deterministisk versionering og korrelerbart logging. Særligt ved individuel virksomhedsoftware med lange livscyklusser sparer I derved tid ved opdateringer og ved integration af eksterne systemer.

Konklusion: Er indsatsen det værd — og hvornår tipper tilgangen?

Værdien opstår, når jeres REST-grænseflade ikke bare „fungerer“, men er driftsmæssigt holdbar: stabile JSON-kontrakter, versionering uden URL-vildvækst, gennemskuelige fejl og debugging uden gætterier. Netop her er tilgangen med Context, Correlation-ID og centralt Exception-Mapping i RemObjects SDK stærk.

Anvendelsesgrænser: Hvis I kun har et enkelt, kortlivet endpoint uden integrationspartnere, virker Media-Type-Versionierung hurtigt som overengineering. Snapshot-logging giver kun mening, hvis I disciplineret implementerer Redaction og Aktivering. Og: Hvis jeres proxy-stack headers ‚optimerer‘ eller fjerner, skal I først rette infrastrukturen op, ellers debugger I det forkerte lag.

Hvis I skal modernisere en eksisterende Delphi-serverlandskab eller integrere en procesnær softwareløsning rent i ERP/DMS/CRM, er netop disse mekanismer ofte forskellen mellem „kører i test“ og „kører i drift“.

På det faglige område spiller også Delphi REST-API og REST-server og Remobjects Sdk Delphi en vigtig rolle, når integrationer, dataflows og videreudvikling skal fungere problemfrit sammen.

Drøft projekt eller moderniseringsprojekt med Net-Base.

Næste trin

Når et emne bliver til et reelt projekt, bør arkitektur, eksisterende systemer og drift tidligt vurderes samlet.

Vi støtter ikke kun ved enkeltspørsmål, men også når kildekodeudsnit, legacy-komponenter eller portalidéer skal udvikles til et robust virksomhedsprojekt.

  • Eksisterende tilstand, målbillede og tekniske risici vurderes samlet.
  • REST, dataadgang, portaler og idrulning bliver ikke udskudt som eftertanker.
  • I ser tidligt, hvilken vej der er økonomisk og driftsmæssigt holdbar.

Del indlæg

Del dette indlæg direkte

LinkedIn, X, XING, Facebook, WhatsApp og e-mail er straks tilgængelige. Til Instagram forbereder vi link og kort tekst med det samme.

E-mail

Instagram åbner i en ny fane. Linket og kortteksten kopieres på forhånd til udklipsholderen.