Net-Base Magasin

09.06.2026

REST API med RemObjects SDK: ryddig versjonering og feilsøking av JSON-endepunkter (Delphi Source-utdrag)

Hvordan du med RemObjects SDK i Delphi bygger et REST API som ikke svikter i drift: stabile JSON-kontrakter, versjonering uten URL-kaos, Correlation-ID gjennom alle lag, sentralisert Error-Mapping, Snapshot-logging for krevende debug-tilfeller samt praksisnære anbefalinger...

09.06.2026

Fra magasinetema til prosjektpraksis

Egnede tjeneste- og tekniske sider for innlegget

Hvorfor „REST API med RemObjects SDK“ i praksis ofte avgjør ved grensetilfeller

En REST API med RemObjects SDK avgjøres sjelden av «Hello World»-servicen, men av de punktene der drift, legacy og integrasjon kolliderer: versjonering uten nedetid, konsistent feilhåndtering over alle endepunkter, reproduserbar feilsøking i proxy-kjeder og evnen til entydig å korrelere forespørsler når det oppstår problemer.

RemObjects SDK leverer mye infrastruktur for dette: tjenester, meldingsformater, serialisering, hosting (f.eks. som Windows- und Linux-Services eller bak IIS/Reverse Proxy) og definerte steder for sentral feilbehandling. Det som ofte mangler i modne forretningsprogramvare-landskap, er en konsekvent gjennomført kontrakt: Hvilke JSON-felt er stabile? Hvordan signaliserer vi feil? Hvordan gjenkjenner vi en forespørsel når den har gått gjennom lastbalanser, TLS-terminering og flere backend-lag?

Følgende tilnærming (inklusive Delphi-snutten) viser en robust linje for RemObjects SDK: versionere JSON-kontrakter, tvinge Correlation-ID (request-ID for sporing), oversette Exceptions til HTTP-status og JSON-feilobjekter og samtidig unngå å sette feilsøking og drift opp mot hverandre. I tillegg ser vi på randtilfeller som ofte opptrer i ekte miljøer: threading på serveren, database-tilgang ved BDE-avløsning med native tilkobling, proxy-headere, timeouts og skitne klient-payloads.

Arkitektur-Entscheidung: Versionierung über Medien-Typ statt URL

Mange API-er versjonerer via stier som /v1/. Det er pragmatisk, men i langvarige integrasjoner (f.eks. ERP/DMS/CRM-tilkoblinger) fører det ofte til URL-duplisering, doble ruter, doble tester og «Hvilken versjon bruker vi egentlig?» i driftsmanualene.

Et alternativ er versjonering via Media Type (Content Negotiation). Klienten sender f.eks. Accept: application/vnd.company.order+json;v=2. Serveren leser versjonen deterministisk og tilpasser kontrakt-/DTO-oppførsel. Dette fungerer i proxy- og cache-kjeder hvis headerne blir videreført korrekt. For administratorer er det dessuten lett å verifisere: En forespørsel kan gjenskapes med Curl/Postman uten at URL-ene må variere.

RemObjects SDK er ikke «REST-puristisch», men et pragmatisk service-rammeverk. Nettopp derfor lønner medietype-varianten seg: Dere kan beholde stabile endepunkter og samtidig videreutvikle kontrakter. Viktig er at dere alltid evaluerer versjonen, bestemmer sentralt på ett sted og overfører resultatet til service-konteksten.

När svikter Accept-Header-varianten?

I praksis finnes det tre typiske sviktpunkter som bør adresseres på forhånd:

  • Proxy-Policies: Noen Reverse Proxies/WAF-regler normaliserer eller filtrerer Accept-header. Da faller API-en stille tilbake til standard. Løsning: Sjekk proxy-reglene eksplisitt, eventuelt ty til X-Api-Version som fallback.
  • Client-Libraries: Noen HTTP-klienter setter egne Accept-header og overskriver verdier. Løsning: Støtt kontraktsversjon også som en valgfri query-parameter (kun som fallback), eller parse Accept-headeren tolerant på serversiden.
  • Caching: Hvis response-caching er i bruk, må cachen variere etter Accept (Vary: Accept), ellers leverer den versjon 1 til versjon 2-klienter. Løsning: sett Vary eksplisitt, eller deaktiver caching på API-nivå.
  • Kildeutdrag: Request-Context, Correlation-ID, versjon og Error-Mapping

    Koden er bevisst laget for å kunne integreres i eksisterende RemObjects-serverprosjekter: et lite context-lag, en parser for API-versjonen (fra Accept), en Correlation-ID-mekanisme og et sentralt exception-mapping. Begreper:

    • Correlation-ID: Entydig ID per forespørsel som dukker opp igjen i responsen og refereres i logger.
    • Exception-Mapping: Oversettelse av interne Delphi-exceptions til stabile feilobjekter som klienten kan håndtere (inkl. HTTP-status).
    • Contract-Version: Versjon av JSON-kontraktet som styrer oppførsel 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 forespørselskontekst i stedet for «et eller annet i threadlocal»

    Snippeten skiller bevisst: TApiContext er den minimale tilstanden du ønsker å sende videre. I RemObjects SDK går mye via server-/kanalkontekst. I heterogene prosjekter (f.eks. ekstra worker-tråder, DB-kø, bakgrunnsjobber) er eksplisitt videreføring ofte mer robust enn implisitte threadlocals, fordi det gjør samtidighet og kontekstbytter mer synlige.

    Forutsetninger: Accept-header-varianten forutsetter at din reverse proxy (nginx, IIS ARR, Traefik) videresender headeren uendret. I noen miljøer blir «uvanlige» Accept-headere filtrert eller slått sammen.

    Fallgruver: Versjonering via Accept er bare så god som testene dine. Hvis klienter bruker biblioteker som overskriver Accept, kan en API plutselig falle tilbake til standard. For legacy-klienter er en standard-fallback fornuftig, men den må være synlig i overvåkningen (f.eks. logg-advarsel «Version defaulted»).

    Alternativ: Hvis du foretrekker å gjøre versjonering via X-Api-Version: Parseren er identisk, bare kilden er en annen header. Fra gateways sitt ståsted er dette noen ganger enklere å kontrollere.

    Integrasjon i RemObjects SDK: Correlation-ID og Exception-Mapping ved serviceinngang

    Den egentlige effekten oppstår når du bruker mekanismen konsekvent i serverens kant: én gang ved request-innslag for å lese fra headerne, og én gang ved exception-utgang for å oversette til en stabil response. Avhengig av hosting (f.eks. RO-HTTP-Server, IIS-hosting, egenkjørte Windows-/Windows- og Linux-Services) varierer de konkrete hook-punktene; prinsippet er det samme: bygge kontekst, kalle forretningslogikk, mappe exceptions sentralt.

    I RemObjects-prosjekter jobber man ofte direkte per servicemetode. Det skalerer i starten, men svikter i drift: hver metode implementerer logging og feilhåndtering forskjellig. Et ryddig skjæringspunkt er en Service-Basis eller en Dispatcher som standardiserer.

    Praktisk fremgangsmåte (bevisst kort og implementeringsnær)

    1. Les Correlation-ID fra request-header X-Correlation-ID; hvis den mangler, generer den på serversiden (f.eks. GUID).
    2. Les kontraktsversjon fra Accept (eller fra X-Api-Version).
    3. Logg request-start: metode, sti, Correlation-ID, remote IP, start måling av varighet.
    4. Utfør forretningslogikk; kapsle DB-tilgang så transaksjonelt som mulig.
    5. Fang exceptions: bestem HTTP-status, opprett JSON-feilobjekt, sett response-header X-Correlation-ID.
    6. Logg request-slutt: status, varighet, eventuelt feilkode.

    Trådhåndtering i serveren: Hvorfor Correlation-ID uten kontekstdisiplin blir verdiløs

    Et vanlig Delphi-tilfelle: Service-metoden utløser asynkront arbeid (f.eks. rapportgenerering, import, push til et DMS). Da er ikke lenger den opprinnelige request-tråden den som skriver logglinjene senere. Hvis Correlation-ID bare er kjent «i starten», faller sporbarheten sammen.

    Pragmatiske regel: Alt som ikke blir værende i request-tråden, skal få konteksten eksplisitt overlevert. Selv om det ser ut som flere parameterlister, lønner det seg. Alternativt kan man bruke et klart definert kontekstobjekt som bevisst overleveres til workere (i stedet for globale variabler eller skjulte singletons).

    Typiske vippepunkter i RemObjects-/Delphi-servere:

    • DB-tilkoblinger per tråd: BDE-Ablosung mit nativer Anbindung-Verbindungen kan ikke automatisk deles trådsikkert. En Connection-Pool eller én forbindelse per tråd er ofte mer hensiktsmessig enn „en global Connection“.
    • Transaksjonsgrenser: Hvis du har flere steg i en Request som hører sammen, må transaksjonen holde seg innen samme logiske enhet. Asynkront arbeid må ikke «ved et uhell» fortsette i den samme transaksjonen.
    • Avbrytelse: Når klienten avbryter (Proxy timeout, Browser closed), fortsetter serveren ofte å kjøre. Vurder bevisst om bakgrunnsarbeid fortsatt gir mening da.

    Datatilgang og feilkoder: 409 er ikke „også en 500“

    I integrasjonsprosjekter er ryddig error-mapping mer enn kosmetikk. Det avgjør om en motpart (ERP-Connector, ETL-Job, kundeportal) kan reagere korrekt. Noen praktiske retningslinjer som har vist seg i Delphi/RemObjects-miljøer:

    • 400 Bad Request: Validering, manglende/ugyldige parametere, JSON ikke parsebar. Viktig: Responsen må være stabil selv om body er ødelagt.
    • 401/403: Skille autentisering og autorisasjon. 401 betyr «ingen/ugyldig identitet», 403 «identitet ok, men forbudt».
    • 404: Ressurs finnes ikke. Vær forsiktig med sikkerhet: Ikke alltid oppgi om noe eksisterer.
    • 409 Conflict: Faglig konflikt (f.eks. versjonskonflikt, «status tillater ikke denne handlingen», unik nøkkelkonflikt når det er faglig relevant).
    • 422 Unprocessable Content: Når syntaktisk er alt ok, men faglig validering feiler (ikke alle team bruker 422, men det er ofte tydeligere enn 400).
    • 500: Alt du ikke kan klassifisere tydelig. Dette inkluderer også «DB down», «Timeout», «Unhandled Exception».

    Delphi-spesifikk triks: Mange DB-feil kommer som generiske Exceptions. Det lønner seg å i data-tilgangslaget målrettet sjekke for kjente situasjoner og overføre dem til EApiError. Viktig: Ikke ta med SQL-fragmenter eller interne tabell-/kolonnenavn i klientmeldingen. Disse detaljene hører i loggen, ikke i responsen.

    Debugging-triks: reproduserbare feil gjennom „Contract Snapshot“

    Uvanlig, men i drift ekstremt nyttig: Lagre ved feil (eller målrettet for bestemte Correlation-IDs) et «Snapshot» av Request-Headern + Request-Body i en debug-spool-fil. Dette er ikke kontinuerlig logging (personvern/volum), men et kontrollert verktøy for å gjenskape vanskelige å reprodusere tilfeller nært produksjon.

    Viktig: Et Snapshot må aldri persistere ufiltrerte Auth-Header, Tokens eller personopplysninger. I praksis betyr det: Redaction (maskering) og aktivering kun via feature-flag eller whitelist (f.eks. bare for bestemte Correlation-IDs, korte tidsvinduer).

    Ryddig implementering i praksis: Maskering i stedet for å utelate

    I reelle integrasjoner er ofte de «kritiske» feltene nettopp de man trenger for debugging (f.eks. identifikatorer). I stedet for generell utelatelse er maskering bedre: delvis erstatte token, behold kun domenet for e-post, IBAN bare de siste sifrene. Slik forblir tilfellet reproduserbart uten å spre unødvendige data i filsystemet. I tillegg bør Snapshotet være klart merket som et debug-artefakt og ha en definert oppbevaringstid.

    Sikkerhet og drift: header-videresending, proxy-kjeder og timeouts

    En REST API ender sjelden direkte ved klienten. Typisk er kjeder av reverse proxy, TLS-terminering, WAF eller API-gateway. Dette gir praktiske punkter:

    • Remote IP: Ikke stol blint på X-Forwarded-For. Ta den kun fra betrodde proxier og ellers bruk den direkte socket-IP-en. I driftsdokumentasjonen bør det stå hvilke hopp som er „trusted“.
    • Timeouts: Hvis proxyen har 30 sekunder, men backendet ditt trenger 2 minutter, vil du få Ghost-Requests. Sett timeouter konsistent langs kjeden og avgjør: synkront Request eller Job-Pattern (202 Accepted + Status-Endpunkt).
    • Correlation-ID: Sett Correlation-ID i response-headerne, slik at administratorer kan korrelere den mot logger og klientside. Hvis et gateway bruker egne Request-IDs: logg og koble begge ID-ene.
    • Fehlertexte: I produksjon ingen interne detaljer. Debug-detaljer kun kontrollert (Stage/Feature-Flag) og i tvilstilfeller kun i loggen.

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

    I Delphi-økosystemer bygges REST-Server ofte med lettere rammeverk (f.eks. minimalistiske HTTP-routere). RemObjects SDK viser sin styrke når du allerede har eller trenger en flerlaget arkitektur:

    • Klare servicegrenser: Servicemetoder er eksplisitte, kontrakter er versjonerbare.
    • Transporter og serialisering: Du kan bruke JSON, men også andre meldingsformater (avhengig av oppsett), uten å blande inn forretningslogikken.
    • Drift: Hosting-alternativer og integrasjon i eksisterende Windows- og Linux-tjenester er planbare, inkludert ryddige utrullinger.

    Den viste tilnærmingen kompletterer dette med de delene som ofte mangler i hverdagen: enhetlige feilobjekter, deterministisk versjonering og korrelerbar logging. Spesielt for skreddersydd bedriftsprogramvare med lange livssykluser sparer du tid ved oppdateringer og ved integrasjon med eksterne systemer.

    Konklusjon: Lønner innsatsen seg — og hvor bryter tilnærmingen sammen?

    Merverdien oppstår når din REST-grensesnitt ikke bare „fungerer“, men er varig driftbar: stabile JSON-kontrakter, versjonering uten URL-villvoksing, etterprøvbare feil og debugging uten gjetting. Nettopp her er tilnærmingen med Context, Correlation-ID og sentralt Exception-Mapping i RemObjects SDK sterk.

    Begrensninger: Hvis du bare har ett enkelt, kortlivet endepunkt uten integrasjonspartnere, fremstår Media-Type-versjonering raskt som overengineering. Også Snapshot-Logging gir bare mening hvis du disiplinert implementerer Redaction og aktivering. Og: Hvis proxy-stacken din «optimaliserer» eller fjerner headere, må du først rette opp infrastrukturen, ellers feilsøker du i feil lag.

    Hvis du moderniserer et eksisterende Delphi-serverlandskap eller må integrere en prosessnær programvareløsning pent inn i ERP/DMS/CRM, er nettopp disse mekanismene ofte forskjellen mellom „kjører i test“ og „kjører i drift“.

    I det faglige miljøet spiller også Delphi REST-API og REST-server og Remobjects Sdk Delphi en viktig rolle, når integrasjoner, dataflyt og videreutvikling må fungere sømløst.

    Diskuter prosjekt eller moderniseringsprosjekt med Net-Base.

    Neste trinn

    Når et tema blir et reelt prosjekt, bør arkitektur, eksisterende systemer og drift vurderes samlet allerede tidlig i prosessen.

    Vi bistår ikke bare med enkeltspørsmål, men også når kodesnutter, legacy-temaer eller portalideer skal utvikles til et robust virksomhetsprosjekt.

    • Eksisterende tilstand, målbildet og tekniske risikoer vurderes samlet.
    • REST, datatilgang, portaler og utrulling blir ikke utsatt som etterfølgende oppgaver.
    • Dere ser tidlig hvilken vei som er økonomisk og driftsmessig levedyktig.

    Del innlegg

    Del dette innlegget direkte

    LinkedIn, X, XING, Facebook, WhatsApp og e-post er umiddelbart tilgjengelige. For Instagram forbereder vi lenke og kort tekst umiddelbart.

    E-post

    Instagram åpnes i en ny fane. Lenken og kortteksten kopieres først til utklippstavlen.