Frå magasinetema til prosjektpraksis
Passande teneste- og tekniske sider til innlegget
Kvifor „REST API mit RemObjects SDK“ i praksis ofte avgjer på randane
Ein REST API mit RemObjects SDK står og fell sjeldan på «Hello World»-servicen, men på stadene der drift, legacy og integrasjon kolliderer: versjonshandtering utan stopp, konsekvent feilhandsaming over alle endepunkt, reproducerbar feilsøking gjennom proxy-kjeder og evna til entydig korrelasjon av requests ved problem.
RemObjects SDK leverer mykje infrastruktur for dette: tenester, meldingsformat, serialisering, hosting (t.d. som Windows- und Linux-Services eller bak IIS/Reverse Proxy) og definerte punkt for sentral feilhandsaming. Det som i etablerte business-software-landskap ofte manglar, er ein konsekvent gjennomført kontrakt: Kva JSON-felt er stabile? Korleis signaliserer vi feilar? Korleis kjenner vi igjen ein request etter at han har passert load balancer, TLS-terminering og fleire backend-lag?
Følgjande tilnærming (inkludert Delphi-snipsel) skisserer ei robust linje for RemObjects SDK: JSON-kontraktar versjonerast, Correlation-ID (Request-ID for sporing) krevjast, Exceptions i HTTP-status og JSON-feilobjekt omsetjast, samtidig som feilsøking og drift ikkje vert stilt opp mot kvarandre. I tillegg ser vi på randtilfelle som i reelle miljø jamleg opptrer: threading på server, database-tilgang ved BDE-avløysing med nativer bindingar, proxy-headarar, timeouts og «skitne» klient-payloads.
Arkitekturvedtak: Versjonering via mediatype i staden for URL
Mange API-ar versjonerer gjennom stiar som /v1/. Det er pragmatisk, men i langtlevande integrasjonar (t.d. ERP/DMS/CRM-tilkoplingar) fører det ofte til duplisering av URL-ar, doble ruter, dobbel testing og spørsmålet «Kva versjon brukar vi eigentleg?» i driftsdokumentasjonen.
Eit alternativ er versjonering via mediatype (content negotiation). Klienten sender t.d. Accept: application/vnd.company.order+json;v=2. Serveren les versjonen deterministisk og tilpassar kontrakt/DTO-oppførsel tilsvarande. Dette fungerer i proxy- og cache-kjeder når headerane blir vidareført korrekt. For administratorar er det dessutan lett å verifisere: ein request kan reproducerast med Curl/Postman utan at URL-ane skil seg frå kvarandre.
RemObjects SDK er ikkje «REST-puristisk», men eit pragmatisk service-rammeverk. Nett av den grunn løner mediametype-varianten seg: De kan behalde stabile endepunkt og likevel utvikle kontraktar vidare. Viktig er at de alltid evaluerer versjonen, avgjer sentralt eitt stader og tek resultatet inn i service-konteksten.
Kven rådner Accept-header-varianten?
I praksis finst det tre typiske sviktpunkt som bør handterast på førehand:
- Proxy-policyar: Nokre reverse-proxyar/WAF-reglar normaliserer eller filtrerer Accept-header. Då fell API-et stille tilbake til standard. Løysing: sjekk proxy-reglane eksplisitt, eventuelt fall tilbake på
X-Api-Version. - Client-bibliotek: Enkelte HTTP-klientar set eigne Accept-headerar og overskriv verdiar. Løysing: støtt kontraktsversjon også som ein valfri query-parameter (berre som fallback), eller parse Accept-headeren server-side med tolerant logikk.
- Caching: Når Response-Caching er i bruk, må cachen variere etter
Accept(Vary: Accept), elles leverer han versjon 1 til versjon-2-klientar. Løysing: settVarymedvite, eller deaktiver caching på API-nivå.
Kildeutdrag: Request-Context, Correlation-ID, Versjon og Error-Mapping
Koden er med vilje skoren slik at han let seg integrere i eksisterande RemObjects-serverprosjekt: eit lite Context-lag, ein parser for API-versjonen (frå Accept), ein Correlation-ID-mekanisme og eit sentralt Exception-Mapping. Omgrep:
- Correlation-ID: Entydig ID per request som går att i response og som blir referert i loggar.
- Exception-Mapping: Omsetjing av interne Delphi-Exceptions til stabile, klientbehandlingslege feilobjekt (inkl. HTTP-Status).
- Contract-Version: Versjon av JSON-kontrakten som styrer åtferd og felt.
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 staden for „ein eller annan Threadlocal”
Snutten skil medvite: TApiContext er den minimale tilstanden du vil sende med. I RemObjects SDK går mykje gjennom server-/channel-kontekst. I heterogene prosjekt (til dømes ekstra Worker-Threads, DB-Queue, bakgrunnsjobbar) er eksplisitt gjennompassing oftare meir robust enn implisitte Threadlocals, fordi du med det gjer samtid og kontekstskifte meir synlege.
Forutsetningar: Accept-header-varianten krev at Reverse Proxy (nginx, IIS ARR, Traefik) vidareformidlar headeren uendra. I nokre miljø blir «uvanlege» Accept-headerar filtrerte eller samla saman.
Fallgruver: Versjonering via Accept er berre så god som testane de har. Om klientbibliotek overskriver Accept, kan ei API plutseleg falle tilbake til default. For legacy-klientar er eit default-fallback fornuftig, men det må vere synleg i overvakinga (t.d. loggvarsel «Version defaulted»).
Variantar: Om de føretrekk å gjere versjonering via X-Api-Version: parseren er identisk, berre kjelda er ein annan header. Frå gateway-synspunkt er det av og til enklare å kontrollere.
Integrasjon i RemObjects SDK: Correlation-ID og Exception-Mapping ved serviceinngang
Den faktiske verknaden kjem når de brukar mekanikken konsekvent i utkanten av serveren: ein gong ved request-inngang lese frå headerar, ein gong ved exception-utfall omsetje til ein stabil response. Avhengig av hosting (til dømes RO-HTTP-Server, IIS-hosting, sjølvdriven Windows-/Windows- og Linux-Services) varierer dei konkrete hook-punkta; prinsippet er det same: bygg Context, kall forretningslogikk, map Exceptions sentralt.
I RemObjects-prosjekt vert det ofte jobba direkte per servicemetode. Det skalerer godt i starten, men sviktar under drift: kvar metode byggjer logging og feilhandtering ulikt. Ein rein skilje er ein Service-Basis eller ein Dispatcher som standardiserer.
Praktisk framgangsmåte (medvite kort og implementasjonsnært)
- Les Correlation-ID frå request-header
X-Correlation-ID; om ho manglar, generer ho server-side (t.d. GUID). - Les kontraktversjon frå
Accept(eller fråX-Api-Version). - Logg request-start: metode, sti, Correlation-ID, remote IP; start varigheitsmåling.
- Utfør forretningslogikk; kapsle DB-tilgang helst transaksjonelt.
- Fang Exceptions: bestem HTTP-status, bygg eit JSON-feilobjekt, sett response-header
X-Correlation-ID. - Logg request-slutt: status, varigheit, eventuelt feilkode.
Trådhandtering i serveren: Kvifor Correlation-ID utan kontekst-disiplin blir verdilaus
Eit vanleg Delphi-kanttilfelle: Servicemetoda triggar asynkront arbeid (til dømes rapportgenerering, import, push til eit DMS). Då er ikkje lenger den opprinnelege request-tråden den som skriv logglinjer seinare. Om Correlation-ID berre er kjend «i starten», fell sporbarheita frå kvarandre.
Pragmatiske regel: Alt som ikkje blir verande strengt i request-tråden, skal få Context eksplisitt overlevert. Sjølv om det ser ut som fleire parameterlister, løner det seg. Alternativt kan ein bruke eit klart definert kontekstobjekt som medvite blir sendt til workerar (i staden for globale variablar eller skjulte singletons).
Typiske vippepunkt i RemObjects-/Delphi-serverar:
- DB-tilkoplingar per tråd: BDE-Ablosung mit nativer Anbindung-tilkoplingar er ikkje automatisk trygt delbare mellom trådar. Ein tilkoplingspool eller ein tilkopling per tråd er ofte meir fornuftig enn «ein global tilkopling».
- Transaksjonsgrenser: Når ein innanfor eit request har fleire steg som høyrer saman, må transaksjonen halde seg innan same logiske eining. Asynkron arbeid må ikkje «ved eit uhell» vidaregå i same transaksjon.
- Avbryting: Når klienten avbryt (proxy-timeout, nettlesar lukka), vil serveren ofte halde fram. Vurder medvite om bakgrunnsarbeid då framleis gir meining.
Dataåtkomst og feilkodar: 409 er ikkje «også ein 500»
I integrasjonsprosjekt er ryddig error-mapping meir enn kosmetikk. Det avgjer om ein motpart (ERP-Connector, ETL-jobb, kundeportal) kan reagere korrekt. Nokre praksisnære retningsliner som har vist seg i Delphi/RemObjects-omgjevnader:
- 400 Bad Request: Validering, manglande/ugyldige parameter, JSON ikkje parsbar. Viktig: Responsen skal vere stabil, sjølv om body er øydelagd.
- 401/403: Skil autentisering og autorisering. 401 betyr «ingen/ugyldig identitet», 403 «identitet ok, men forbode».
- 404: Ressurs finst ikkje. Forsiktig med sikkerheit: Ikkje alltid opplyse om noko finst.
- 409 Conflict: Fagleg konflikt (t.d. versjonskonflikt, «status tillèt ikkje denne handlinga», unike nøkkelbrot dersom det er fagleg relevant).
- 422 Unprocessable Content: Når syntaksen er ok, men fagleg validering feilar (ikkje alle team brukar 422, men det er ofte klarare enn 400).
- 500: Alt som du ikkje kan klassifisere presist. Dette inkluderer også «DB down», «Timeout», «Unhandled Exception».
Delphi-spesifikk tilnærming: Mange DB-feil dukkar opp som generiske Exceptions. Det løner seg å på dataåtkomstlaget målretta sjekke for kjende situasjonar og mappe dei til EApiError. Viktig: Ikkje ta med SQL-fragment eller interne tabell-/kolonnenamn i klientmeldinga. Desse detaljane høyrer i loggen, ikkje i responsen.
Debugging-knep: reproducerbare feil via «Contract Snapshot»
Uvanleg, men i drift ekstremt nyttig: Lagre ved feil (eller målretta for bestemte Correlation-IDs) ein «Snapshot» av Request-Headern + Request-Body i ei debug-spool-fil. Dette er ikkje permanent logging (personvern/volum), men eit kontrollert verkty for å gjenskape vanskeleg reproducerbare tilfelle frå produksjon.
Viktig: Ein Snapshot må aldri persistere ufiltrerte Auth-Header, Tokens eller personopplysningar. I praksis betyr det: Redaction (maskering) og aktivering berre via feature-flag eller whitelist (t.d. berre for bestemte Correlation-IDs, korte tidsvindauge).
Ryddig gjennomføring i praksis: Maskering framfor utelating
I reelle integrasjonar er nettopp dei «kritiske» felta ofte dei ein treng for debugging (t.d. identifikatorar). I staden for generell utelating er maskering betre: delvis erstatte token, behalde berre domenet i e-postadresse, IBAN berre dei siste sifrene. Slik held saka seg reproducerbar utan å spreie unødvendige data i filsystemet. I tillegg bør Snapshotet vere klart merka som eit debug-artefakt og ha definert oppbevaringstid.
Sikkerheit og drift: videresending av header, proxy-kjeder og timeouts
Ein REST API endar sjeldan direkte ved klienten. Typisk ligg det ein kjede av reverse proxy, TLS-terminering, WAF eller API-gateway i mellom. Dette gir praktiske følgjer:
- Fjern-IP: Ikkje stol blindt på
X-Forwarded-For. Ta berre imot denne frå pålitelege proxyar, elles bruk den direkte socket-IP-en. I driftsmanualar bør det stå kva hopp som er pålitelege („trusted“). - Timeouts: Har proxyen 30 sekund og backendet ditt treng 2 minutt, skapar du ghost-requests. Sett timeouts konsistent langs kjeda og avgjer om de skal køyre synkrone request eller nyttar eit jobb-mønster (202 Accepted + status-endepunkt).
- Correlation-ID: Setj Correlation-ID i response-headerar, slik at administratorar kan knyte saman loggar og klientside. Dersom eit gateway nyttar eigne request-ID-ar: logg begge ID-ane og kartlegg samspelet.
- Feilmeldingar: I produksjon ingen interne detaljar i feilmeldingane. Debugdetaljar berre kontrollert (stage/feature-flag) og i tvil berre i logg.
Vurdering: Kvifor RemObjects SDK kan vere ein fordel her
I Delphi-økosystem blir REST-serverar ofte bygd med lettare rammeverk (t.d. minimalistiske HTTP-routerar). RemObjects SDK spelar på si styrke når de allereie har eller treng ei fleirlagsarkitektur:
- Tydelege servicegrenser: Servicemetodar er eksplisitte, kontraktar kan versjonerast.
- Transports og serialisering: De kan kommunisere via JSON, men òg andre meldingformat avhengig av oppsett, utan å blande forretningslogikk og transport.
- Drift: Hosting-alternativ og integrasjon i eksisterande Windows- og Linux-tenester er planbar, inklusive ryddige rollout-prosessar.
Den viste tilnærminga kompletterer dette med dei delane som ofte manglar i kvardagen: einsarta feilmåte, deterministisk versjonshandtering og korrelerbart logging. Særleg for individuell bedriftsprogramvare med lange livsløp sparar dette tid ved oppdateringar og ved integrasjon av eksterne system.
Konklusjon: Lønar innsatsen seg — og når blir tilnærminga for tung?
Meirverdien kjem når din REST-grensesnitt ikkje berre „fungerer“, men er driftbar over tid: stabile JSON-kontraktar, versjonshandtering utan URL-rot, etterprøvbare feil og debugging utan gjetting. Det er her tilnærminga med context, Correlation-ID og sentralt exception-mapping i RemObjects SDK er solid.
Eigenskapar og begrensingar: Om de berre har eit enkelt, kortlivet endepunkt utan integrasjonspartnarar, kan Media-Type-versionering raskt vere overengineering. Også Snapshot-logging gir berre meining dersom de disiplinert implementerer redaction og aktivering. Og: om proxy-stacken dykkar «optimaliserer» eller fjerner headerar, må de retta opp infrastrukturen først — elles endar de opp med å debugge feil lag.
Når de moderniserer ein eksisterande Delphi-serverlandskap eller skal integrere ei prosessnær løysing ryddig inn i ERP/DMS/CRM, er ofte nett desse mekanismane skilnaden mellom „kjører i test“ og „kjører i produksjon“.
I det faglege miljøet spelar òg Delphi REST-API og REST-Server og Remobjects Sdk Delphi ei viktig rolle når integrasjonar, dataflyt og vidareutvikling må spela godt saman.
Neste steg
Når temaet blir eit reelt prosjekt, bør arkitektur, eksisterande system og drift vurderast tidleg saman.
Vi støttar ikkje berre ved enkeltspørsmål, men òg når korte kildekodesnuttar, legacy-tema eller portalidéar skal utviklast til eit robust bedriftsprosjekt.
- Eksisterande tilstand, målbiletet og tekniske risikoar blir vurderast samla.
- REST, datatilgang, portalar og utrulling blir ikkje utsette til seinare som etterverknader.
- De ser tidleg kva veg som er økonomisk og driftsmessig berekraftig.