Od teme v reviji do projektne prakse
Ustrezne strani storitev in tehnični opisi k prispevku
Zakaj „REST API z RemObjects SDK“ v praksi pogosto odloča na robnih primerih
En REST API z RemObjects SDK redko stoji ali pade na „Hello World“-storitev, temveč na mestih, kjer se srečujeta obratovanje, legacy in integracija: verzioniranje brez zaustavitve, dosledno obnašanje napak na vseh endpointih, reproducirljivo razhroščevanje pri verigah proxyjev in sposobnost enoznačnega koreliranja requestov v primeru težav.
RemObjects SDK prinaša veliko infrastrukture: storitve, formate sporočil, serializacijo, hosting (npr. kot Windows- in Linux-storitev ali za IIS/Reverse Proxy) ter definirane točke za centralno obravnavo napak. Kar pa v utrjenih poslovnih programskih okoljih pogosto manjka, je konseventno vpeljana pogodba: Katera JSON-polja so stabilna? Kako signaliziramo napake? Kako request znova prepoznamo, ko je šel skozi load balancer, TLS-terminacijo in več back-end plasti?
Slednji pristop (vključno z Delphi-izrezki) pokaže robustno linijo za RemObjects SDK: JSON-pogodbe verzionirati, Correlation-ID (Request-ID za sledenje) uveljaviti, Exceptions v HTTP-status in JSON-objekte napak preslikati ter hkrati ne postavljati razhroščevanja in obratovanja v nasprotje. Dodatno obravnavamo robne primere, ki se v realnih okoljih redno pojavljajo: threading na strežniku, dostopi do podatkovnih baz z BDE-odpravo z nativno vezavo, proxy-headerji, timeouti in „umažene“ klientske payload-e.
Arhitekturna odločitev: verzioniranje preko medijskega tipa namesto URL
Številne API-je se verzionira preko poti, npr. /v1/. To je pragmatično, vendar pri dolgotrajnih integracijah (npr. povezave ERP/DMS/CRM) pogosto vodi do podvajanja URL-jev, dvojnih rout, dvojnih testov in nejasnosti »katere verzije v resnici uporabljamo?« v operativni dokumentaciji.
Alternativa je verzioniranje preko medijskega tipa (pogajanje o vsebini). Klient pošlje npr. Accept: application/vnd.company.order+json;v=2. Strežnik deterministično prebere verzijo in prilagodi vedenje contracta/DTO. To deluje v verigah proxyjev in cache-jev, če se headerji pravilno posredujejo. Za skrbnike je dodatna prednost, da je preverljivo: request lahko reproducirate z Curl/Postman brez razlik v URL-jih.
RemObjects SDK ni „REST-purističen“, temveč pragmatično service-framework. Ravno zato se medijski-tip pristop izplača: ohranite lahko stabilne endpoint-e in hkrati razvijate pogodbe. Pomembno je, da različico vedno preberete, odločitev sprejmete centralno in rezultat prenesete v kontekst vaše storitve.
Kdaj odpove varianta z Accept-headerjem?
V praksi obstajajo trije tipični prelomi, ki jih je smiselno nasloviti vnaprej:
- Proxy-politike: Nekateri reverse proxyji/WAF-pravila normalizirajo ali filtrirajo Accept-header. V tem primeru vaša API tiho preide na privzeto. Rešitev: izrecno preverite proxy-pravila, po potrebi uporabite
X-Api-Versionkot rezervno možnost. - Klientske knjižnice: Nekateri HTTP-klienti nastavijo svoje Accept-headerje in prepišejo vrednosti. Rešitev: podprite verzijo contracta tudi kot opcijski query-parameter (samo kot fallback) ali na strežni strani tolerantno parsirajte Accept-header.
- Caching: Če se uporablja predpomnjenje odgovorov, mora predpomnilnik razlikovati po glavi
Accept(Vary: Accept), sicer bo pošiljal različico 1 klientom, ki zahtevajo različico 2. Rešitev: namerno nastavitiVaryali onemogočiti predpomnjenje na ravni API.
Izsek iz izvorne kode: Request-Context, Correlation-ID, različica in preslikava napak
Koda je namenoma oblikovana tako, da se jo lahko vključi v obstoječe RemObjects-server projekte: majhen sloj konteksta, parser za verzijo API (iz Accept), mehanizem Correlation-ID in centralno Exception-Mapping. Pojmi:
- Correlation-ID: Enoličen ID za vsak zahtevek, ki se pojavi tudi v odzivu in po katerem se sklicujejo v logih.
- Exception-Mapping: Prevod notranjih Delphi-Exceptions v stabilne, s klientom obdelljive objekte napak (vključno z HTTP-statusom).
- Contract-Version: Različica JSON-pogodbe, ki krmili vedenje in polja.
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.Namen: Stalen kontekst zahteve namesto „nekje v threadlocal“
Izsek namerno loči: TApiContext je minimalno stanje, ki ga želite predajati naprej. V RemObjects SDK se veliko dogaja preko konteksta strežnika/kanala. V heterogenih projektih (npr. dodatni worker-threadi, DB-queue, ozadni opravki) je eksplicitno predajanje pogosto bolj robustno kot implicitni threadlocali, saj z njim naredite sočasnost in zamenjave konteksta bolj vidne.
Omejitve: Varianta z Accept-headerjem predpostavlja, da vaš reverse proxy (nginx, IIS ARR, Traefik) header prenese nespremenjen. V nekaterih okoljih so „nenavadni“ Accept-headerji filtrirani ali združeni.
Pastke: Verzija preko Accept je dobra toliko, kolikor so vaši testi. Če klienti uporabljajo knjižnice, ki prepišejo Accept, se lahko API nenadoma vrne na privzeto. Za legacy-kliente je smiselno imeti privzeto vračilo, vendar mora biti to vidno v monitoringu (npr. log-opozorilo „Version defaulted“).
Variant: Če raje delate verzioniranje preko X-Api-Version: parser je identičen, le vir je drug header. Z vidika gatewayev je to včasih lažje nadzorovati.
Integracija v RemObjects SDK: Correlation-ID in exception-mapping ob vstopu v servis
Pravi učinek nastane, če mehaniko konsekventno uporabite na robu vašega strežnika: enkrat ob vstopu zahteve preberete iz headerjev, enkrat ob izhodu izjeme prevedete v stabilen odgovor. Glede na hosting (npr. RO-HTTP-Server, IIS-hosting, samostojno upravljani Windows-/Windows- in Linux-Services) se konkretne točke za hook razlikujejo; načelo pa ostaja enako: zgraditi kontekst, klicati poslovno logiko, izjeme centralno preslikati.
V RemObjects-projektih se pogosto dela neposredno na nivoju posamezne service-metode. To sprva dobro skalira, vendar pri obratovanju odpove: vsaka metoda gradi logging in obravnavo napak drugače. Čista ločnica je osnova servisa ali dispatcher, ki standardizira.
Praktični potek (namenoma kratek in implementacijsko naravnan)
- Preberite Correlation-ID iz request-headerja
X-Correlation-ID; če manjka, ga strežnik ustvari (npr. GUID). - Preberite verzijo contracta iz
Accept(ali izX-Api-Version). - Zabeležite začetek requesta: metoda, pot, Correlation-ID, oddaljeni IP, začnite merjenje trajanja.
- Izvedite poslovno logiko; dostope do DB po možnosti zapakirajte transakcijsko.
- Ujemite izjemo: določite HTTP-status, ustvarite JSON-objekt napake, nastavite response-header
X-Correlation-ID. - Zabeležite konec requesta: status, trajanje, po potrebi šifro napake.
Threading na strežniku: Zakaj je Correlation-ID brez discipline konteksta brez vrednosti
Hitro pojavni Delphi-robni primer: servisna metoda sproži asinhrono delo (npr. generiranje poročil, import, push v DMS). Tedaj začetni request-thread ni več tisti, ki kasneje piše log-vrstice. Če je Correlation-ID znana le „na začetku“, sledljivost razpade.
Pragmatično pravilo: vse, kar ne ostane striktno v request-threadu, dobi kontekst eksplicitno predan. Tudi če to pomeni več seznamov parametrov, se izplača. Alternativno lahko delate s jasno definiranim objektom konteksta, ki ga namensko predate workerjem (namesto globalnih spremenljivk ali skritih singletonov).
Tipični prelomni trenutki v RemObjects-/Delphi-strežnikih:
- DB-povezave na nit: BDE-Ablosung mit nativer Anbindung-povezave niso avtomatsko varne za sočasno rabo v več nitih. Connection-Pool ali ena povezava na nit je pogosto smiselnejša kot „globalna povezava“.
- Meje transakcij: Če imate znotraj enega requesta več korakov, ki spadajo skupaj, mora transakcija ostati znotraj iste logične enote. Asinhrono delo ne sme „po naključju“ teči naprej v isti transakciji.
- Preklic (Cancellation): Če klient prekine (proxy timeout, brskalnik zaprt), strežnik pogosto teče dalje. Premislite, ali ima ozadinsko delo v takšnem primeru še smisel.
Dostop do podatkov in kode napak: 409 ni „tudi 500“
V integracijskih projektih je dosledno preslikavanje napak več kot kozmetika. Odloča, ali lahko nasprotna stran (ERP-Connector, ETL-job, portal za stranke) pravilno reagira. Nekaj praktičnih vodil, ki so se obnesla v Delphi/RemObjects-okoljih:
- 400 Bad Request: Validacija, manjkajoči/neveljavni parametri, JSON ni parsabilen. Pomembno: odgovor naj ostane stabilen, tudi če je body poškodovan.
- 401/403: Ločite avtentikacijo in avtorizacijo. 401 pomeni „brez ali neveljavna identiteta“, 403 pa „identiteta je v redu, vendar je dostop prepovedan“.
- 404: Vir ne obstaja. Previdnost z varnostjo: ne razkrivajte vedno, ali nekaj obstaja.
- 409 Conflict: Strokovni/poslovni konflikt (npr. konflikt verzij, „status ne dovoljuje te akcije“, kršitev unikatnega ključa, če ima to poslovni pomen).
- 422 Unprocessable Content: Če je sintaktično vse v redu, a ne uspe poslovna validacija (ne uporablja vsaka ekipa 422, vendar je pogosto jasneje kot 400).
- 500: Vse, kar ne morete dosledno klasificirati. Sem sodijo tudi „DB down“, „Timeout“, „Unhandled Exception“.
Delphi-specifičen trik: Veliko DB-napak se dvigne kot generične izjeme. Izplača se na nivoju dostopa do podatkov ciljano preverjati znane situacije in jih prevesti v EApiError. Pomembno: ne vključujte SQL-fragmentov ali notranjih imen tabel/kolon v sporočilo za klienta. Ti podatki sodijo v log, ne v response.
Debugging-trik: reproducibilne napake z »Contract Snapshot«
Neobičajno, a v obratovanju izjemno koristno: ob napakah (ali ciljano za določene Correlation-IDs) shranite „snapshot“ iz Request-Headerjev + Request-Body v datoteko Debug-Spool. To ni stalno logiranje (varstvo podatkov/obseg), temveč kontrolirano orodje za ponovitev težko reproducibilnih primerov blizu produkcije.
Pomembno: Snapshot nikoli ne sme nefiltrirano persistirati Auth-Headerjev, tokenov ali osebnih podatkov. V praksi to pomeni: redaction (maskiranje) in aktivacija le preko feature-flag ali bele liste (npr. samo za določene Correlation-IDs, za kratka časovna okna).
Čista izvedba v praksi: maskiranje namesto izpuščanja
V resničnih integracijah so ravno „kritična“ polja pogosto tista, ki jih potrebujete za debug (npr. identifikatorji). Namesto splošnega izpuščanja je bolje maskirati: delno zamenjati tokene, pri e-pošti obdržati le domeno, pri IBAN-u le zadnje števke. Tako ostane primer reproducibilen, brez raztrosanja nepotrebnih podatkov po datotečnem sistemu. Poleg tega naj bo snapshot jasno označen kot debug-artifakt in naj ima določeno časovno obdobje hrambe.
Varnost in obratovanje: posredovanje headerjev, proxy-verige in časovne omejitve
API REST redko konča neposredno pri odjemalcu. Tipično so vmes verige Reverse Proxy, TLS-terminacija, WAF ali API-Gateway. Iz tega izhajajo praktične točke:
- Oddaljeni IP: Ne zanašajte se slepo na
X-Forwarded-For. Sprejemajte ga le od zaupanja vrednih proxyjev, sicer uporabite neposredno socket-IP. V operativnih priročnikih naj bo navedeno, kateri hopi so »zaupanja vredni«. - Časovne omejitve: Če ima proxy 30 sekund, vaše backend pa potrebuje 2 minuti, boste ustvarili ghost-requeste. Določite časovne omejitve konsistentno vzdolž verige in se odločite: sinhroni zahtevek ali vzorec Job (202 Accepted + statusni endpoint).
- Correlation-ID: Nastavite Correlation-ID v response-headerjih, da jo lahko skrbniki združijo iz logov in strani odjemalca. Če gateway uporablja lastne Request-IDs: logirajte in preslikajte obe ID.
- Besedila napak: V produkcijskem okolju brez notranjih podrobnosti. Podrobnosti za debugging le nadzorovano (Stage/Feature-Flag) in v primeru dvoma le v logu.
Uvrstitev: Zakaj je RemObjects SDK tukaj lahko v prednosti
V Delphi-ekosistemih se REST-Server pogosto gradijo z lažjimi ogrodji (npr. minimalistični HTTP-Router). RemObjects SDK pokaže svojo moč, ko že imate ali potrebujete večslojno arhitekturo:
- Jasne meje storitev: metode storitev so eksplicitne, kontrakti podpirajo verzioniranje.
- Transporti in serializacija: Lahko govorite JSON, pa tudi druge formate sporočil (odvisno od nastavitve), brez premešavanja poslovne logike.
- Obratovanje: možnosti hostinga in integracija v obstoječe Windows- in Linux-storitve so načrtljive, vključno z urejenimi uvajanji.
Prikazani pristop dopolni to z elementi, ki pogosto manjkajo v vsakdanjem delu: enotni objekti napak, deterministično verzioniranje in korrelirano beleženje. Pri individualni podjetniški programski opremi z dolgimi življenjskimi cikli vam to prihrani čas pri posodobitvah in pri integraciji zunanjih sistemov.
Zaključek: Ali se trud obrestuje – in kje pristop zaide?
Večja vrednost nastane, ko vaša REST-vmesnik ne le »deluje«, ampak je trajnostno upravljiv: stabilni JSON-pogodki, verzioniranje brez nenadzorovanega razraščanja URL-jev, sledljive napake in razhroščevanje brez ugibanja. Ravno pri tem je pristop s kontekstom, Correlation-ID in centralnim preslikovanjem izjem v RemObjects SDK močan.
Meje uporabe: Če imate le en sam, kratkotrajen endpoint brez integracijskih partnerjev, se versioniranje po mediatipu hitro zdi kot overengineering. Tudi Snapshot-Logging je smiselno le, če disciplinirano implementirate Redaction in Aktivierung. In: če vaš proxy-stack headerje »optimizira« ali odstranjuje, morate najprej urediti infrastrukturo, drugače boste odpravljali napake v napačnem sloju.
Če modernizirate obstoječo Delphi-strežniško krajino ali morate procesno bližnjo programsko rešitev čisto integrirati v ERP/DMS/CRM, so prav ti mehanizmi pogosto razlika med »deluje v testu« in »deluje v obratovanju«.
V strokovnem okolju imajo pomembno vlogo tudi Delphi REST-API in REST-Server in Remobjects Sdk Delphi, kadar morajo integracije, podatkovni tokovi in nadaljnji razvoj delovati usklajeno.
naslednji korak
Ko iz teme nastane resničen projekt, je treba arhitekturo, obstoječe sisteme in obratovanje zgodaj obravnavati skupaj.
Ne podpiramo le pri posameznih vprašanjih, ampak tudi takrat, ko iz izrezkov izvorne kode, legacy-tem ali idej za portale nastane zanesljiv podjetniški projekt.
- Obstoječe stanje, ciljno stanje in tehnična tveganja se ocenjujejo skupaj.
- REST, dostop do podatkov, portali in Rollout ne bodo prestavljeni v kasnejše faze.
- Že zgodaj vidite, katera pot je ekonomsko in operativno vzdržna.