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ænserne

En REST API med RemObjects SDK afgøres sjældent ved „Hello World“-servicen, men ved de steder, hvor drift, Legacy og integration støder sammen: versionsstyring uden stop, konsistent fejladfærd på tværs af alle endepunkter, reproducerbar debugging i proxy-kæder og evnen til entydigt at korrelere forespørgsler i fejltilfælde.

RemObjects SDK leverer til det formål meget infrastruktur: services, meddelelsesformater, serialisering, hosting (f.eks. som Windows- og Linux-Services eller bag IIS/Reverse Proxy) og definerede steder til central fejlhåndtering. Hvad der i etablerede forretningssoftware-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-snipsel) viser en robust linje for RemObjects SDK: versionér JSON-kontrakter, påkræv Correlation-ID (Request-ID til sporing), oversæt Exceptions til HTTP-status og JSON-fejlobjekter og undgå at spille debugging og drift ud imod hinanden. Derudover ser vi på kanttilfælde, som regelmæssigt optræder i reelle miljøer: threading på serveren, databaseadgang i forbindelse med BDE-Ablösung med nativer binding, proxy-headers, timeouts og „beskidte“ klient-payloads.

Arkitekturbeslutning: Versionering via medietype i stedet for URL

Mange API’er versionerer via stier som /v1/. Det er pragmatisk, men i længerevarende integrationer (f.eks. ERP/DMS/CRM-tilknytninger) fører det ofte til duplikation af URLs, dobbelte ruter, dobbelte tests og „Hvilken version bruger vi egentlig?“ i driftshåndbøger.

Et alternativ er versionering via den Media Type (Content Negotiation). Klienten sender f.eks. Accept: application/vnd.company.order+json;v=2. Serveren læser versionen deterministisk og tilpasser contract/DTO-adfærd. Det fungerer i proxy- og cache-kæder, hvis headerne videreføres korrekt. For administratorer er det desuden let at verificere: En request kan reproduceres med Curl/Postman uden at ændre URL’erne.

RemObjects SDK er ikke „REST-puristisch“, men et pragmatisk service-framework. Netop derfor giver medietype-tilgangen mening: I kan bevare stabile endepunkter og alligevel videreudvikle kontrakter. Det er vigtigt, at I altid evaluerer versionen centralt ét sted og fører resultatet ind i jeres service-kontekst.

Hvornår svigter Accept-header-varianten?

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

  • Proxy-politikker: Nogle Reverse Proxies/WAF-regler normaliserer eller filtrerer Accept-header. Så falder jeres API stille tilbage til standard. Løsning: Gennemgå proxy-regler eksplicit, evt. falde tilbage til X-Api-Version.
  • Client-Libraries: Nogle HTTP-clients sætter egne Accept-headers 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 i spil, skal cachen variere efter Accept (Vary: Accept), ellers leverer den version 1 til version-2-klienter. Løsning: sæt Vary bevidst, eller deaktiver caching på API-niveau.
  • Kildeudsnit: Request-Context, Correlation-ID, Version og Error-Mapping

    Koden er bevidst skåret, så den kan integreres i eksisterende RemObjects-serverprojekter: et lille Context-lag, en parser til API-versionen (fra Accept), en Correlation-ID-mekanisme og et centralt Exception-Mapping. Begreber:

    • Correlation-ID: Entydig ID pr. request, som indgår i responsen og kan refereres i logs.
    • Exception-Mapping: Oversættelse af interne Delphi-Exceptions til stabile fejlobjekter, der kan håndteres af klienten (inkl. HTTP-status).
    • Contract-Version: Version af JSON-kontrakten, som 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“

    Uddraget adskiller bevidst: TApiContext er den minimale tilstand, du vil videregive. I RemObjects SDK går meget via server-/channel-kontekst. I heterogene projekter (fx ekstra worker-tråde, DB-queue, baggrundsjob) er eksplicit videregivelse ofte mere robust end implicitte Threadlocals, fordi det gør samtidighed og kontekstskift mere synlige.

    Randbetingelser: Accept-header-varianten forudsætter, at din reverse proxy (nginx, IIS ARR, Traefik) videresender headeren uændret. I nogle miljøer filtreres eller sammenfletter man „uvanlige“ Accept-headere.

    Faldgruber: Versionering via Accept er kun så god som dine tests. Hvis klienter bruger biblioteker, der overskriver Accept, kan en API pludselig falde tilbage til default. For legacy-klienter er et default-fallback fornuftigt, men det skal være synligt i monitorering (fx log-advarsel „Version defaulted“).

    Varianter: Hvis du foretrækker at versionere via X-Api-Version: parseren er identisk, kun kilden er en anden header. Set fra gatewayers perspektiv er det nogle gange nemmere at kontrollere.

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

    Den egentlige effekt opnås, når du anvender mekanikken konsekvent i kanten af din server: læs fra headerne én gang ved request-indgang, og oversæt én gang ved undtagelsesudgang til et stabilt Response. Afhængigt af hosting (fx RO-HTTP-Server, IIS-hosting, selvbetjent Windows-/Windows- og Linux-Services) varierer de konkrete hook-punkter; princippet er det samme: byg context, kald forretningslogik, map undtagelser centralt.

    I RemObjects-projekter arbejder man ofte direkte pr. service-metode. Det skalerer godt i starten, men svigter i drift: Hver metode implementerer logging og fejlhåndtering forskelligt. En ren afgrænsning er en fælles servicebase 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, generer 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 måling af varighed.
    4. Udfør forretningslogik; kapsl DB-adgang så vidt muligt transaktionelt.
    5. Opfang undtagelser: bestem HTTP-status, opret JSON-fejlobjekt, sæt Response-header X-Correlation-ID.
    6. Log request-slut: status, varighed, evt. fejlkode.

    Trådning på serveren: Hvorfor Correlation-ID uden kontekstdisciplin bliver værdiløs

    Et hyppigt Delphi-kanttilfælde: Service-metoden triggere asynkront arbejde (fx rapportgenerering, import, push til et DMS). Så er den oprindelige request-tråd ikke længere den, der senere skriver loglinjer. Hvis Correlation-ID kun er kendt „i starten“, bryder sporbarheden sammen.

    Pragmatisk regel: Alt, der ikke forbliver strengt i request-tråden, får konteksten eksplicit overleveret. Selv om det betyder flere parameterrækker, betaler det sig. Alternativt kan man arbejde med et klart defineret kontekst-objekt, som bevidst gives til workeren (i stedet for globale variabler eller skjulte singletons).

    Typiske brudpunkter i RemObjects-/Delphi-servere:

    • DB-forbindelser per tråd: BDE-Ablosung mit nativer Anbindung-Verbindungen deles ikke automatisk trådsikkert. En Connection-Pool eller én forbindelse per tråd er ofte mere rimelig end „en global Connection“.
    • Transaktionsgrænser: Hvis I inden for et request har flere trin, der hører sammen, skal transactionen forblive i samme logiske enhed. Asynkront arbejde må ikke „ved et uheld“ fortsætte i den samme transaction.
    • Aflysning: Hvis klienten afbryder (proxy timeout, browser lukket), kører serveren ofte videre. Overvej bevidst, om baggrundsarbejde så stadig giver mening.

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

    I integrationsprojekter er konsekvent error-mapping mere end kosmetik. Det afgør, om en modpart (ERP-Connector, ETL-Job, Kundeportal) 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 forblive stabilt, også hvis body er ødelagt.
    • 401/403: Skelnen mellem autentifikation og autorisation. 401 betyder „ingen/ugyldig identitet“, 403 „identitet ok, men forbudt“.
    • 404: Ressource findes ikke. Vær forsigtig med sikkerhed: Afslør ikke altid, om noget eksisterer.
    • 409 Conflict: Faglig konflikt (f.eks. versionskonflikt, „status tillader ikke denne handling“, entydig nøgleovertrædelse, hvis den er fagligt relevant).
    • 422 Unprocessable Content: Når syntaksen er ok, men faglig validering fejler (ikke hvert team bruger 422, men det er ofte tydeligere end 400).
    • 500: Alt, hvad I ikke kan klassificere klart. Det inkluderer også „DB down“, „Timeout“, „Unhandled Exception“.

    Delphi-specifikt trick: Mange DB-fejl kommer 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 sammenhæng: Inddrag ikke SQL-fragmenter eller interne tabel-/kolonnenavne i client-beskeden. Disse detaljer hører i loggen, ikke i response.

    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-headere + Request-body i en debug-spool-fil. Det er ikke permanent logging (databeskyttelse/volumen), men et kontrolleret værktøj til at genskabe svært reproducerbare tilfælde tæt på produktion.

    Vigtigt: Et snapshot må aldrig ufiltreret persistentgøre auth-headere, tokens eller personoplysninger. I praksis betyder det: Redaction (maskering) og aktivering kun via feature-flag eller whitelist (f.eks. kun for bestemte Correlation-IDs, korte tidsvinduer).

    Ren implementering i praksis: Maskering fremfor udeladelse

    I reelle integrationer er det ofte de „kritiske“ felter, man har brug for til debugging (f.eks. identifikatorer). I stedet for blank udeladelse er maskering bedre: Erstat dele af tokens, behold kun domænet i e-mailadresser, IBAN kun de sidste cifre. Så forbliver sagen reproducerbar uden at sprede unødvendige data i filsystemet. Derudover bør snapshot’et være klart markeret som et debug-artefakt og have en defineret opbevaringstid.

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

    En REST API ender sjældent direkte ved klienten. Typisk ses kæder af reverse proxy, TLS-terminering, WAF eller API-gateway. Heraf følger praktiske punkter:

    • Remote IP: Stol ikke blindt på X-Forwarded-For. Accepter kun værdier fra betroede proxies, ellers brug den direkte socket-IP. I driftsmanualer bør det fremgå, hvilke hops der er „trusted“.
    • Timeouts: Hvis en proxy har 30 sekunder, men dit backend kræver 2 minutter, skaber du Ghost-Requests. Sæt timeouts konsistent langs kæden og beslut: synkrone requests eller job-mønster (202 Accepted + status-endpoint).
    • Correlation-ID: Sæt Correlation-ID i response-headere, så administratorer kan korrelere dem mellem logs og klientside. Hvis et gateway bruger egne request-IDs: log og kortlæg begge IDs.
    • Fejltekster: I produktionsdrift ingen interne detaljer. Debug-oplysninger kun kontrolleret (stage/feature-flag) og i tvivlstilfælde kun i loggen.

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

    I Delphi-økosystemer bygges REST-server ofte med lettere frameworks (fx minimalistiske HTTP-routere). RemObjects SDK udspiller sine styrker, når I allerede har eller har brug for en flerlagsarkitektur:

    • Klare servicegrænser: Servicemetoder er eksplicitte, kontrakter kan versionsstyres.
    • Transporte og serialisering: I kan tale JSON, men også andre meddelelsesformater (afhængig af setup), uden at forretningslogik blandes sammen.
    • Drift: Hostingmuligheder og integration i eksisterende Windows- og Linux-services er planlæggelige, inklusive kontrollerede udrulninger.

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

    Konklusion: Betaler indsatsen sig – og hvor bryder tilgangen sammen?

    Værdien opstår, når jeres REST-interface ikke bare «virker», men er holdbar i drift: stabile JSON-kontrakter, versionering uden URL-rod, forståelige fejl og debugging uden gætterier. Netop her står tilgangen med Context, Correlation-ID og centralt exception-mapping i RemObjects SDK stærkt.

    Begrænsninger: Hvis I kun har et enkelt, kortlivet endpoint uden integrationspartnere, vil media-type-versionering hurtigt virke som overengineering. Snapshot-logging giver kun mening, hvis I disciplineret implementerer redaction og aktivering. Og: hvis jeres proxy-stack „optimerer“ eller fjerner headers, skal infrastrukturen rettes først, ellers debugger I på den forkerte lag.

    Hvis I moderniserer en eksisterende Delphi-serverlandskab eller skal 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“.

    I det faglige domæne spiller også Delphi REST-API og REST-server og Remobjects Sdk Delphi en vigtig rolle, når integrationer, datatrømme og videreudvikling skal fungere gnidningsløst.

    Drøft projekt eller moderniseringsprojekt med Net-Base.

    Næste trin

    Når emnet 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 udrulning bliver ikke udskudt som efterfølgende opgaver.
    • De ser tidligt, hvilken vej der er økonomisk og driftsmæssigt bæredygtig.

    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.

    E-mail

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