Od teme magazina do projektne prakse
Povezane stranice usluga i tehnologije za članak
Zašto „REST API s RemObjects SDK“ u praksi često odlučuje na rubovima
Jedan REST API s RemObjects SDK rijetko posluje ili pada na „Hello World“-servisu, nego na mjestima gdje operacije, legacy i integracija dolaze u konflikt: verzioniranje bez zastoja, konzistentno ponašanje pri greškama preko svih endpointa, reproducibilno debuggiranje kroz lance proxyja i sposobnost jednoznačne korelacije requestova u slučaju problema.
RemObjects SDK donosi mnogo infrastrukture: servise, formate poruka, serializaciju, hosting (npr. kao Windows- i Linux-servisi ili iza IIS/Reverse Proxy) i definirane točke za centralno upravljanje greškama. Ono što u razvijenim poslovnim softverskim okruženjima često nedostaje jest dosljedno proveden ugovor: koja JSON polja su stabilna? Kako signaliziramo greške? Kako ponovno identificirati request koji je prošao kroz load balancer, TLS-terminaciju i više backend-slojeva?
Sljedeći pristup (uključujući Delphi-snippete) pokazuje robusnu liniju za RemObjects SDK: verzioniranje JSON ugovora, Correlation-ID (Request-ID za praćenje) nametnuti, mapiranje Exceptions u HTTP-status i JSON objekt(e) grešaka i pritom ne suprotstavljati debugiranje i operativu. Dodatno sagledavamo rubne slučajeve koji se u stvarnim okruženjima redovito pojavljuju: upravljanje nitima na serveru, pristupi bazi podataka s BDE-zamjena s native vezom, proxy zaglavlja, timeouti i „prljave“ klijentske payloadove.
Arhitektonska odluka: verzioniranje preko medijskog tipa umjesto URL-a
Mnoge API-je verzioniraju preko putanja kao što je /v1/. To je pragmatično, ali u dugotrajnim integracijama (npr. ERP/DMS/CRM konekcije) često vodi dupliciranju URL-ova, dvostrukim rutama, dvostrukim testovima i pitanju „koju verziju zapravo koristimo?“ u operativnim priručnicima.
Alternativa je verzioniranje preko Media Type (Content Negotiation). Klijent pošalje npr. Accept: application/vnd.company.order+json;v=2. Server deterministički iščita verziju i prilagodi ponašanje contracta/DTO-a. To funkcionira u lancima proxyja i cache-a ako se headeri ispravno prosljeđuju. Za administratore je dodatno lako provjerljivo: request se može reproducirati preko curl-a/Postmana bez promjene URL-ova.
RemObjects SDK nije „REST-purističan“, već pragmatičan service-framework. Upravo zato varijanta preko medijskog tipa ima smisla: možete zadržati stabilne endpoint-e i istovremeno razvijati ugovore. Važno je da verziju uvijek evaluirate, da centralno odlučite i da rezultat unesete u kontekst vašeg servisa.
Kada varijanta s Accept-headerima zakaže?
U praksi postoje tri uobičajene kritične točke koje treba unaprijed adresirati:
- Proxy-Policies: Neki reverse proxyji/WAF pravila normaliziraju ili filtriraju Accept-zaglavlja. Tada vaša API tiho padne na default. Rješenje: eksplicitno provjeriti proxy-pravila, po potrebi preći na
X-Api-Version. - Client-Libraries: Neki HTTP-klijenti postavljaju vlastita Accept-zaglavlja i prepisuju vrijednosti. Rješenje: podržati verziju contracta i kao opcionalni query-parametar (samo kao fallback), ili parsati Accept-zaglavlje server-side tolerantno.
- Keširanje: Ako se koristi keširanje odgovora, keš mora varirati prema
Accept(Vary: Accept), inače će isporučiti verziju 1 klijentima verzije 2. Rješenje: eksplicitno postavitiVaryili onemogućiti keširanje na razini API-ja.
Izvorni isječak: Request-Context, Correlation-ID, verzija i mapiranje pogrešaka
Kod je namjerno složen tako da se može integrirati u postojeće RemObjects-server projekte: mala sloj za kontekst, parser za verziju API-ja (iz Accept), mehanizam Correlation-ID i centralno mapiranje iznimki. Pojmovi:
- Correlation-ID: Jedinstveni ID po zahtjevu koji se vraća u odgovoru i na koji se referencira u logovima.
- Exception-Mapping: Prevođenje internih Delphi-Exceptions u stabilne, klijentima obradive objekte pogrešaka (uključujući HTTP-status).
- Contract-Version: Verzija JSON ugovora koja upravlja ponašanjem i poljima.
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.Svrha: Stabilan kontekst zahtjeva umjesto „negdje u ThreadLocalu”
Ovaj isječak svjesno odvaja: TApiContext je minimalno stanje koje želite proslijediti. U RemObjects SDK mnogo toga se odvija preko Server-/Channel-Kontexts. U heterogenim projektima (npr. dodatni Worker-Threads, DB-Queue, zadaci u pozadini) eksplicitno prosljeđivanje je često robusnije od implicitnih Threadlocals, jer time činite konkurentnost i promjene konteksta vidljivijima.
Preduvjeti: Varijanta s Accept-headerom pretpostavlja da vaš reverse proxy (nginx, IIS ARR, Traefik) prosljeđuje header nepromijenjen. U nekim okruženjima se „neobični“ Accept-headeri filtriraju ili spajaju.
Zamke: Verzije preko Accept su samo toliko pouzdane koliko i vaši testovi. Ako klijenti koriste biblioteke koje prepisuju Accept, API može iznenada pasti na zadanu vrijednost. Za legacy klijente smislen je zadani fallback, ali on mora biti vidljiv u monitoringu (npr. log upozorenje „Version defaulted“).
Varijante: Ako radije želite verzioniranje preko X-Api-Version: parser je identičan, samo je izvor drugi header. Iz perspektive gatewaya to je ponekad lakše kontrolirati.
Integracija u RemObjects SDK: Correlation-ID i mapiranje iznimki pri ulazu servisa
Stvarna korist nastaje kada mehaniku konzekventno primijenite na rubu vašeg servera: jednom pri ulasku zahtjeva pročitati iz headera, jednom pri izlazu iz iznimke prevesti u stabilan response. Ovisno o hostingu (npr. RO-HTTP-Server, IIS-hosting, vlastiti Windows-/Windows- und Linux-Services) razlikuju se konkretne hook-punkte; princip ostaje isti: sastaviti kontekst, pozvati poslovnu logiku, centralno mapirati iznimke.
U RemObjects projektima se često radi izravno po svakoj metodi servisa. To na početku dobro skalira, ali u radu može postati problematično: svaka metoda uređuje logging i obradu grešaka na svoj način. Čist rez je service-baza ili dispatcher koji standardizira te korake.
Praktični postupak (namjerno kratak i implementacijski orijentiran)
- Pročitati Correlation-ID iz Request-Headera
X-Correlation-ID; ako nedostaje, generirati ga na serverskoj strani (npr. GUID). - Pročitati verziju kontrakta iz
Accept(ili izX-Api-Version). - Zabilježiti početak zahtjeva: metoda, putanja, Correlation-ID, udaljena IP, započeti mjerenje trajanja.
- Izvršiti poslovnu logiku; pristupe bazi po mogućnosti kapsulirati transakcijski.
- Uhvatiti iznimku: odrediti HTTP status, stvoriti JSON objekt greške, postaviti Response-Header
X-Correlation-ID. - Zabilježiti kraj zahtjeva: status, trajanje, eventualno kod greške.
Threading na serveru: Zašto Correlation-ID bez discipline konteksta postaje bezvrijedna
Čest rubni slučaj u Delphi-okruženjima: metoda servisa pokreće asinkroni rad (npr. generiranje izvještaja, import, push u DMS). Tada izvorni request-thread više nije onaj koji kasnije zapisuje logove. Ako je Correlation-ID poznata samo „na početku“, pratljivost se raspada.
Pragmatično pravilo: Sve što ne ostane striktno u request-threadu treba dobiti kontekst eksplicitno. Čak i ako to izgleda kao duže liste parametara, isplati se. Alternativno se može raditi s jasno definiranim objektom konteksta koji se svjesno prosljeđuje workerima (umjesto globalnih varijabli ili skrivenih singletona).
Tipične kritične točke u RemObjects-/Delphi-serverima:
- DB-veze po niti: BDE-Ablosung mit nativer Anbindung-veze nisu automatski sigurno dijeljive između niti. Connection-pool ili po niti vlastita veza često je prikladnija od „globalne veze“.
- Granice transakcije: Ako unutar jednog zahtjeva imate više koraka koji pripadaju zajedno, transakcija mora ostati u istoj logičkoj jedinici. Asinkroni rad ne smije se „slučajno“ nastaviti u istoj transakciji.
- Prekid (Cancellation): Ako klijent prekine (proxy timeout, zatvoren preglednik), server često nastavlja raditi. Promislite svjesno ima li pozadinski rad tada još smisla.
Pristup podacima i kodovi pogrešaka: 409 nije „također 500“
U integracijskim projektima precizno mapiranje pogrešaka je više od kozmetike. Ono odlučuje može li suprotna strana (ERP-connector, ETL-job, klijentski portal) ispravno reagirati. Nekoliko praktičnih smjernica koje su se pokazale u Delphi/RemObjects-okruženjima:
- 400 Bad Request: Validacija, nedostajući/nerazumni parametri, JSON se ne može parsirati. Važno: Odgovor treba ostati stabilan čak i ako je body korumpiran.
- 401/403: Razdvojite autentifikaciju i autorizaciju. 401 znači „nema/neispravni identitet“, 403 znači „identitet u redu, ali zabranjeno“.
- 404: Resurs ne postoji. Pažnja u pogledu sigurnosti: ne treba uvijek otkrivati postoji li nešto.
- 409 Conflict: Strukturni/funkcionalni konflikt (npr. konflikt verzija, „status ne dopušta ovu akciju“, kršenje jedinstvenog ključa ako je relevantno za poslovnu logiku).
- 422 Unprocessable Content: Ako je sintaksa u redu, ali poslovna validacija ne prolazi (ne koriste sva tima 422, ali često je jasnije od 400).
- 500: Sve što ne možete jasno klasificirati. To uključuje i „DB nedostupna“, „timeout“, „Unhandled Exception“.
Delphi-specifični trik: Mnoge greške baze podataka dolaze kao generičke iznimke. Vrijedi ciljano na sloju pristupa podacima provjeriti poznate situacije i prevesti ih u EApiError. Važno pri tome: Ne preuzimajte fragmente SQL-a ili interne nazive tablica/kolona u poruku klijentu. Ti se detalji trebaju zapisati u log, a ne u response.
Trik za debugiranje: reproducibilne greške pomoću „Contract Snapshot“
Neobično, ali u radu iznimno korisno: spremite pri pogreškama (ili ciljano za određene Correlation-ID-e) „snapshot“ od zaglavlja zahtjeva + tijelo zahtjeva u debug-spool datoteku. To nije trajno logiranje (zaštita podataka/volumen), nego kontrolirani alat za reproduciranje teško reproducibilnih slučajeva u blizini produkcije.
Važno: Snapshot nikad ne smije nefiltrirano persistirati auth-zaglavlja, tokene ili osobne podatke. U praksi to znači: redaction (maskiranje) i aktivacija samo preko feature-flaga ili whiteliste (npr. samo za određene Correlation-ID-e, kratka vremenska prozora).
Čista provedba u praksi: maskiranje umjesto izostavljanja
U stvarnim integracijama upravo su „kritična“ polja često ona koja su potrebna za debug (npr. identifikatori). Umjesto općeg izostavljanja, bolje je maskiranje: djelomično zamijeniti tokene, e‑poštu zadržati samo domenu, IBAN samo posljednje znamenke. Tako je slučaj reproducibilan bez nepotrebnog širenja podataka po datotečnom sustavu. Dodatno, snapshot treba jasno označiti kao artefakt za debugiranje i imati definirano vrijeme čuvanja.
Sigurnost i rad: prosljeđivanje zaglavlja, lanci proxyja i timeouti
Jedna REST API rijetko završava izravno kod klijenta. Tipični su lanci reverse proxyja, TLS-terminacije, WAF ili API-gateway. Iz toga proizlaze praktične točke:
- Remote IP: Nemojte se slijepo pouzdavati u
X-Forwarded-For. Prihvaćajte ga samo od pouzdanih proxyja, u suprotnom koristite izravnu socket-IP. U priručnicima za rad treba navesti koji hopovi „trusted“ su. - Timeouts: Ako proxy ima 30 sekundi, a vaše backend treba 2 minute, stvarate ghost-zahtjeve. Postavite timeoute dosljedno duž lanca i odlučite: sinkroni zahtjev ili job-pattern (202 Accepted + statusni endpoint).
- Correlation-ID: Postavite Correlation-ID u response-zaglavlja, kako bi administratori mogli upariti zapise iz logova i klijentske strane. Ako gateway koristi vlastite Request-ID-e: logirajte i mapirajte obje ID-e.
- Fehlertexte: U produkciji bez internih detalja. Debug-detalje samo kontrolirano (Stage/Feature-Flag) i, u nedoumici, samo u logu.
Einordnung: Warum RemObjects SDK hier im Vorteil sein kann
U Delphi-ekosustavima se REST-Server često grade s lakšim frameworkima (npr. minimalistički HTTP-routeri). RemObjects SDK pokazuje svoju snagu kada već imate ili trebate višeslojnu arhitekturu:
- Klare Service-Grenzen: Jasne granice servisa: metode servisa su eksplicitne, kontrakti su verzionabilni.
- Transporte und Serialisierung: Transporti i serializacija: Možete koristiti JSON, ali i druge formate poruka (ovisno o postavkama), bez miješanja poslovne logike.
- Betrieb: Operativnost: Opcije hostinga i integracija u postojeće Windows- und Linux-Services su planabilne, uključujući uredne rolloute.
Pokazani pristup nadopunjuje to dijelovima koji često nedostaju u svakodnevici: jedinstveni objekti pogrešaka, determinističko verzioniranje i korelirajuće logiranje. Posebno kod individualnog poslovnog softvera s dugim životnim ciklusima time štedite vrijeme pri nadogradnjama i pri integraciji vanjskih sustava.
Fazit: Lohnt sich der Aufwand – und wo kippt der Ansatz?
Dodana vrijednost nastaje kada vaše REST-sučelje ne samo „funkcionira“, već je i dugoročno održivo: stabilni JSON-ugovori, verzioniranje bez URL-divljanja, razumljive pogreške i debugiranje bez nagađanja. Upravo tu je pristup s Context, Correlation-ID i centralnim Exception-Mappingom u RemObjects SDK snažan.
Einsatzgrenzen: Ako imate samo jedan pojedinačni, kratkotrajan endpoint bez integracijskih partnera, Media-Type-verzioniranje brzo djeluje kao overengineering. Snapshot-logiranje također ima smisla samo ako disciplinirano implementirate Redaction i Aktivierung. I: ako vaš proxy-stack zaglavlja „optimiert“ ili uklanja, prvo morate srediti infrastrukturu, inače debugirate pogrešni sloj.
Ako modernizirate postojeće Delphi-serversko okruženje ili trebate čisto integrirati procesno blisko softversko rješenje u ERP/DMS/CRM, upravo su ti mehanizmi često razlika između „läuft im Test“ i „läuft im Betrieb“.
U stručnom okruženju također imaju važnu ulogu Delphi REST-API i REST-Server i Remobjects Sdk Delphi kada integracije, tokovi podataka i daljnji razvoj moraju neometano surađivati.
Sljedeći korak
Kad se tema pretvori u stvarni projekt, arhitektura, postojeći sustav i operativni rad trebaju se rano sagledati zajedno.
Podržavamo vas ne samo u pojedinačnim pitanjima, već i kada iz isječaka izvornog koda, naslijeđenih sustava ili ideja za portale treba nastati pouzdan poslovni projekt.
- Postojeće stanje, ciljna slika i tehnički rizici procjenjuju se zajedno.
- REST, pristup podacima, portali i Rollout neće biti odgođeni kao kasne posljedice.
- Vidite rano koji je put ekonomski i operativno održiv.