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-Versionsom 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.
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.
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)
- Les Correlation-ID fra request-header
X-Correlation-ID; hvis den mangler, generer den på serversiden (f.eks. GUID). - Les kontraktsversjon fra
Accept(eller fraX-Api-Version). - Logg request-start: metode, sti, Correlation-ID, remote IP, start måling av varighet.
- Utfør forretningslogikk; kapsle DB-tilgang så transaksjonelt som mulig.
- Fang exceptions: bestem HTTP-status, opprett JSON-feilobjekt, sett response-header
X-Correlation-ID. - 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.