Od témy magazínu k projektovej praxi
Súvisiace stránky služieb a technológií k príspevku
Prečo „REST API s RemObjects SDK“ v praxi často rozhoduje na okrajoch
REST API s RemObjects SDK zvyčajne nezlyháva pri „Hello World“ servise, ale presne tam, kde sa pretínajú prevádzka, legacy riešenia a integrácia: verzionovanie bez odstávky, konzistentné správanie pri chybách na všetkých endpointoch, reprodukovateľné ladenie pri proxy-reťazcoch a schopnosť jednoznačne korelovať requesty v prípade problémov.
RemObjects SDK prináša pre tieto scenáre veľa infraštruktúry: služby, formáty správ, serializáciu, hosting (napr. ako Windows- a Linux-services alebo za IIS/Reverse Proxy) a definované body na centralizované spracovanie chýb. Čo však v rastúcich podnikových softvérových krajinách často chýba, je konzekventne zavedený kontrakt: ktoré polia v JSON sú stabilné? Ako signalizujeme chyby? Ako znova identifikujeme request, keď prešiel Load Balancerom, TLS-termináciou a niekoľkými vrstvami backendu?
Nasledujúci prístup (vrátane Delphi-snippetov) ukazuje robustnú líniu pre RemObjects SDK: versionovanie JSON-kontraktov, vynucovanie Correlation-ID (Request-ID na sledovanie), prekladanie Exceptions do HTTP-statusov a JSON-chybových objektov a pritom nesystémové protirečenie medzi ladením a prevádzkou. Okrem toho sa pozrieme na okrajové prípady, ktoré sa v reálnych prostrediach pravidelne vyskytujú: threading na serveri, prístupy do databázy pri BDE-odstraňovaní s natívnym pripojením, proxy-headery, time-outy a „špinavé“ klientské payloady.
Architektonické rozhodnutie: verzionovanie cez Medien-Typ namiesto URL
Mnohé API verzionujú cez cesty ako /v1/. Je to pragmatické, ale pri dlhodobejších integráciách (napr. pripojenia ERP/DMS/CRM) to často vedie k duplikácii URL, duplicitným routám, duplicitným testom a k otázke v prevádzkových manuáloch „ktorú verziu vlastne používame?“
Alternatívou je verzionovanie cez Media Type (Content Negotiation). Klient pošle napr. Accept: application/vnd.company.order+json;v=2. Server deterministicky prečíta verziu a upraví správanie kontraktu/DTO. Funguje to v proxy- a cache-reťazcoch, ak sa hlavičky preposielajú bez úprav. Pre administrátorov je to tiež dobre overiteľné: request sa dá reprodukovať cez Curl/Postman bez rozdielu v URL.
RemObjects SDK nie je „REST-puristické“, ale pragmatické servisné framework. Práve preto má zmysel varianta s Media Type: môžete zachovať stabilné endpointy a zároveň vyvíjať kontrakty ďalej. Dôležité je vždy vyhodnocovať verziu na jednom centrálnom mieste a výsledok previesť do kontextu služby.
Kedy zlyhá varianta s Accept-Headerom?
V praxi existujú tri typické body zlomu, ktoré je potrebné predom adresovať:
- Proxy-Policies: Niektoré reverse proxy alebo pravidlá WAF normalizujú alebo filtrujú Accept-header. Vaše API potom ticho padne na default. Riešenie: explicitne skontrolovať pravidlá proxy, prípadne sa uchýliť k
X-Api-Versionako fallback. - Client-Libraries: Niektoré HTTP-knižnice nastavujú vlastné Accept-headery a prepíšu hodnoty. Riešenie: podporiť verziu kontraktu tiež ako nepovinný query-parameter (len ako fallback) alebo serverovo tolerovať a robustne parsovať Accept-header.
Accept (Vary: Accept), inak doručí verziu 1 klientom verzie 2. Riešenie: vedome nastaviť Vary alebo deaktivovať cachovanie na úrovni API.Ukážka zdrojového kódu: Request-Context, Correlation-ID, verzia a Error-Mapping
Kód je zámerne upravený tak, aby sa dal integrovať do existujúcich RemObjects-serverových projektov: malá vrstva kontextu, parser pre API-verziu (z Accept), mechanizmus Correlation-ID a centrálne Exception-Mapping. Pojmy:
- Correlation-ID: Jedinečné ID pre každý Request, ktoré sa vráti v odpovedi a na ktoré sa odkazuje v logoch.
- Exception-Mapping: Preklad interných Delphi-Exceptions do stabilných, klientom spracovateľných objektov chýb (vrátane HTTP-Status).
- Contract-Version: Verzia JSON-kontraktu, ktorá riadi správanie a polia.
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.Účel: Stabilný kontext požiadavky namiesto „irgendwo im Threadlocal“
Útržok rozdeľuje zámerne: TApiContext je minimálny stav, ktorý chcete odovzdávať. V RemObjects SDK veľa beží cez server-/channel-kontext. V heterogénnych projektoch (napr. dodatočné worker-thready, DB-queue, background joby) je explicitné odovzdávanie často robustnejšie než implicitné Threadlocals, pretože tak robíte súbežnosť a zmeny kontextu viditeľnejšími.
Okolnosti: Varianta cez Accept-hlavičku predpokladá, že váš reverse proxy (nginx, IIS ARR, Traefik) hlavičku preposiela nezmenenú. V niektorých prostrediach sú „neobvyklé“ Accept-hlavičky filtrované alebo zjednotené.
Úskalia: Verzionovanie cez Accept je len také dobré, ako sú vaše testy. Ak klienti používajú knižnice, ktoré Accept prepíšu, API môže náhle spadnúť na predvolenú verziu. Pre legacy klietov je predvolený fallback rozumný, ale musí byť viditeľný v monitoringu (napr. log-warning „Version defaulted“).
Varianty: Ak preferujete verzovanie cez X-Api-Version: parser je identický, len je zdroj iná hlavička. Z pohľadu gatewayov je to niekedy jednoduchšie kontrolovať.
Integrácia do RemObjects SDK: Correlation-ID a Exception-Mapping pri vstupe do servisu
Skutočný efekt vznikne, keď mechaniku konzistentne použijete na okraji vášho servera: raz pri vstupe requestu čítať z hlavičiek, raz pri výstupe z výnimky mapovať do stabilnej response. Podľa hostingu (napr. RO-HTTP-Server, IIS-Hosting, vlastné Windows-/Windows- a Linux-services) sa konkrétne hook-pointy líšia; princíp je rovnaký: postaviť Context, zavolať business logiku, výnimky centrálne namapovať.
V RemObjects-projektoch sa často pracuje priamo na úrovni každej service-metódy. To na začiatku škáluje dobre, no pri prevádzke to kolabuje: každá metóda buduje logging a spracovanie chýb inak. Čisté oddelenie je základ pre služby alebo dispatcher, ktorý štandardizuje správanie.
Praktický postup (vedome stručný a implementačne orientovaný)
- Prečítať Correlation-ID z request-hlavičky
X-Correlation-ID; ak chýba, vygenerovať server-side (napr. GUID). - Prečítať contract-verziu z
Accept(alebo zX-Api-Version). - Zalogovať začiatok requestu: metóda, cesta, Correlation-ID, remote IP, spustiť meranie trvania.
- Spustiť business logiku; DB-prístupy kde možno zapuzdriť transakčne.
- Zachytiť Exception: určiť HTTP-status, vytvoriť JSON-chybový objekt, nastaviť Response-Header
X-Correlation-ID. - Zalogovať koniec requestu: status, trvanie, prípadne chybový kód.
Threading na serveri: Prečo je Correlation-ID bez kontextovej disciplíny bezcenná
Bežný Delphi-okrajový prípad: service-metóda spustí asynchrónnu prácu (napr. generovanie reportu, import, push do DMS). Potom pôvodný request-thread už nie je ten, ktorý neskôr zapisuje logy. Ak je Correlation-ID známa len „na začiatku“, sledovateľnosť sa rozpadne.
Pragmatické pravidlo: Všetko, čo neostáva striktne v request-threade, dostane kontext explicitne odovzdaný. Aj keď to vyzerá ako dlhšie zoznamy parametrov, vyplatí sa to. Alternatívne môžete použiť jasne definovaný kontext-objekt, ktorý zámerne odovzdáte workerom (namiesto globálnych premenných alebo skrytých singletonov).
Typické kritické body v RemObjects-/Delphi-serveroch:
- DB-Connections na vlákno: BDE-Ablosung mit nativer Anbindung-Verbindungen sind nicht automatisch thread-sicher teilbar. Ein Connection-Pool oder pro Thread eine Verbindung ist häufig sinnvoller als „eine globale Connection“.
- Transaktionsgrenzen: Ak máte v rámci jednej požiadavky niekoľko krokov, ktoré k sebe patria, musí transakcia zostať v tej istej logickej jednotke. Asynchrónna práca nesmie „náhodou“ pokračovať v tej istej transakcii.
- Cancellation: Keď klient preruší (Proxy timeout, Browser closed), server často beží ďalej. Zvážte vedome, či má pozadová práca v takom prípade ešte zmysel.
Prístup k dátam a chybové kódy: 409 ist nicht „auch ein 500“
V integračných projektoch je čisté mapovanie chýb viac než kozmetika. Rozhoduje, či protistrana (ERP-Connector, ETL-Job, zákaznícky portál) dokáže korektne reagovať. Niekoľko praktických pravidiel, ktoré sa osvedčili v Delphi/RemObjects‑prostrediach:
- 400 Bad Request: Validácia, chýbajúce/neplatné parametre, JSON neparsovateľný. Dôležité: Odpoveď má zostať stabilná aj keď je telo poškodené.
- 401/403: Autentifikáciu a autorizáciu oddeľte. 401 znamená „žiadna/neplatná identita“, 403 „identita OK, ale zakázané“.
- 404: Zdroj neexistuje. Pozor pri bezpečnosti: Nie je vždy vhodné prezradiť, či niečo existuje.
- 409 Conflict: Doménny konflikt (napr. konflikt verzií, „Status erlaubt diese Aktion nicht“, porušenie unikátneho kľúča, ak je to významné z pohľadu domény).
- 422 Unprocessable Content: Keď je syntaktika v poriadku, ale zlyhá doménová validácia (nie každý tím používa 422, no často je to jasnejšie než 400).
- 500: Všetko, čo nedokážete presne zaradiť. Patrí sem aj „DB down“, „Timeout“, „Unhandled Exception“.
Delphi-špecifický trik: Mnohé DB‑chyby sa zobrazujú ako generické výnimky. Oplatí sa v dátovej vrstve cielene kontrolovať známe situácie a previesť ich do EApiError. Dôležité: Neprenášajte do správy klientovi SQL‑fragmenty ani interné názvy tabuliek/stĺpcov. Tieto detaily patria do logu, nie do response.
Debugging‑Trick: reprodukovateľné chyby cez „Contract Snapshot“
Neobvyklé, ale v prevádzke mimoriadne užitočné: Pri chybách (alebo cielene pre určité Correlation‑IDs) uložte „Snapshot“ z Request‑Headern + Request‑Body do debug‑spool súboru. Nie je to trvalé logovanie (ochrana údajov/objem), ale kontrolovaný nástroj na nahrávanie ťažko reprodukovateľných prípadov z blízkosti produkcie.
Dôležité: Snapshot nesmie nikdy nepretržite ukladať autentifikačné hlavičky, tokeny alebo osobné údaje. V praxi to znamená: Redaction (maskovanie) a aktivácia len cez feature‑flag alebo whitelist (napr. len pre konkrétne Correlation‑IDs, krátke časové okná).
Čisté prevedenie v praxi: Maskovanie namiesto vynechávania
V reálnych integráciách sú práve „kritické“ polia často tie, ktoré by ste pri debugovaní potrebovali (napr. identifikátory). Namiesto všeobecného vymazávania je lepšie maskovať: čiastočne nahradiť tokeny, ponechať e‑mail len s doménou, IBAN len s poslednými číslicami. Tak zostane prípad reprodukovateľný bez zbytočného šírenia údajov v súborovom systéme. Navyše by mal byť Snapshot jasne označený ako debug‑artefakt a mať definovanú dobu uchovania.
Bezpečnosť a prevádzka: odovzdávanie hlavičiek, proxy reťazce a time-outy
REST API zriedka končí priamo na kliente. Typické sú reťazce z reverse proxy, TLS-terminácie, WAF alebo API-gateway. Z toho vyplývajú praktické body:
- Remote IP: Nespoliehajte sa slepo na
X-Forwarded-For. Akceptujte ho iba od dôveryhodných proxy a inak použite priamu socket-IP. V prevádzkových príručkách musí byť uvedené, ktoré hop-y sú „trusted“. - Timeouts: Ak má proxy 30 sekúnd, ale vaše backendy potrebujú 2 minúty, vytvárate falošné požiadavky. Nastavte time-outy konzistentne naprieč reťazcom a rozhodnite sa: synchroný request alebo job-pattern (202 Accepted + status-endpoint).
- Correlation-ID: Umiestnite Correlation-ID aj do response-hlavičiek, aby administrátori mohli spojiť záznamy z logov a klientskú stranu. Ak gateway používa vlastné Request-ID: logujte a mapujte obe ID.
- Fehlertexte: V produkčnej prevádzke žiadne interné detaily. Debug-detalje len kontrolovane (Stage/Feature-Flag) a v prípade pochybností iba v logu.
Zaradenie: Prečo môže mať RemObjects SDK tu výhodu
V Delphi-ekosystémoch sa REST-servery často stavajú s ľahšími frameworkmi (napr. minimalistické HTTP-routery). RemObjects SDK ukáže svoje silné stránky, keď už máte alebo potrebujete viacvrstvovú architektúru:
- Jasné hranice služieb: Service-metódy sú explicitné, kontrakty sú verzionovateľné.
- Transporty a serializácia: Môžete komunikovať v JSON, ale aj inými message-formátmi (podľa nastavenia), bez premiešania doménovej logiky.
- Prevádzka: Hostingové možnosti a integrácia do existujúcich Windows- a Linux-servicov sú plánovateľné, vrátane čistých roll-outov.
Ukázaný prístup dopĺňa časti, ktoré v každodennej praxi často chýbajú: jednotné chybové objekty, deterministické verzionovanie a korelovateľné logovanie. Najmä pri individuálnom firemnom softvéri s dlhými životnými cyklami to šetrí čas pri aktualizáciách a pri integrácii externých systémov.
Záver: Stojí to za námahu — a kde sa prístup prekvalifikuje?
Pridaná hodnota vzniká, keď vaše REST rozhranie nielen „funguje“, ale je trvalo prevádzkyschopné: stabilné JSON-kontrakty, verzionovanie bez nekontrolovaného množstva URL, vysledovateľné chyby a debugovanie bez hádania. Práve tu je prístup s Context, Correlation-ID a centrálnym mapovaním výnimiek v RemObjects SDK silný.
Hraničné podmienky použitia: Ak máte len jediný, krátkodobý endpoint bez integračných partnerov, Media-Type-Versionierung rýchlo pôsobí ako overengineering. Aj snapshot-logging dáva zmysel len ak disciplinovane implementujete redaction a aktiváciu. A: ak váš proxy-stack hlavičky „optimalizuje“ alebo odstraňuje, musíte najprv upraviť infraštruktúru, inak budete debugovať nesprávnu vrstvu.
Ak modernizujete existujúcu Delphi serverovú krajinu alebo potrebujete procesne-približnú softvérovú riešenie čisto integrovať do ERP/DMS/CRM, práve tieto mechanizmy často rozhodujú o tom, či systém „beží v teste“ alebo „beží v prevádzke“.
V odbornom prostredí zohrávajú tiež dôležitú úlohu Delphi REST-API a REST-Server a Remobjects Sdk Delphi, ak musia integrácie, dátové toky a ďalší vývoj bezchybne spolupracovať.
ďalší krok
Keď sa z témy stane reálny projekt, architektúru, existujúci stav a prevádzku treba včas posudzovať spoločne.
Podporujeme nielen pri jednotlivých otázkach, ale aj vtedy, keď sa z fragmentov zdrojového kódu, tém súvisiacich s legacy systémami alebo nápadov na portál má stať robustný podnikový projekt.
- Stav, cieľový obraz a technické riziká sa hodnotia spoločne.
- REST, prístup k údajom, portály a nasadenie nebudú odložené na neskôr ako následné úlohy.
- Včas identifikujete, ktorá cesta je ekonomicky a prevádzkovo životaschopná.