Od tématu magazínu k projektové praxi
Vhodné stránky služeb a technické stránky k příspěvku
Proč „REST API s RemObjects SDK“ v praxi často rozhoduje na okrajích
Jedno REST API s RemObjects SDK málokdy stojí nebo padá na „Hello World“ servisu, ale na místech, kde se střetává provoz, legacy a integrace: verzování bez přerušení, konzistentní chování při chybách napříč všemi koncovými body, reprodukovatelné ladění v proxy řetězcích a schopnost jednoznačně korelovat požadavky v případě problémů.
RemObjects SDK přináší pro to hodně infrastruktury: služby, formáty zpráv, serializaci, hosting (např. jako Windows- und Linux-Services nebo za IIS/Reverse Proxy) a definovaná místa pro centrální zpracování chyb. Co ale v narostlých podnikově softwarových krajinách často chybí, je důsledně prosazená smlouva: Která pole JSON jsou stabilní? Jak signalizujeme chyby? Jak znovu rozpoznáme požadavek, když prošel load balancerem, TLS-terminací a několika vrstvami backendu?
Následující přístup (včetně Delphi-ukázek) ukazuje robustní linii pro RemObjects SDK: verzování JSON kontraktů, vynucení Correlation-ID (ID požadavku pro sledování), překlad výjimek do HTTP statusu a JSON chybových objektů a přitom nedávat ladění a provoz do protikladu. K tomu se podíváme na krajní případy, které v reálných prostředích pravidelně nastávají: threading na serveru, přístupy k databázi při BDE-Ablosung mit nativer Anbindung, proxy hlavičky, timeouts a „špinavé“ payloady klientů.
Architektonické rozhodnutí: verzování přes mediální typ místo URL
Mnoho API verzování provádí přes cesty jako /v1/. To je pragmatické, ale v dlouhodobých integracích (např. ERP/DMS/CRM napojení) to často vede k duplikaci URL, duplicitním routám, duplicitním testům a k otázce „Kterou verzi vlastně používáme?“ v provozních příručkách.
Alternativou je verzování přes Media Type (Content Negotiation). Klient pošle např. Accept: application/vnd.company.order+json;v=2. Server deterministicky přečte verzi a přizpůsobí chování kontraktu/DTO. Funguje to v proxy a cache řetězcích, pokud jsou hlavičky korektně předány dál. Pro administrátory je navíc dobře ověřitelné: požadavek lze reprodukovat pomocí curl/Postman, aniž by se lišily URL.
RemObjects SDK není „REST-puristický“, ale pragmatický service-framework. Právě proto se vyplatí varianta s typem média: můžete zachovat stabilní endpointy a přesto vyvíjet kontrakty dál. Důležité je, abyste verzi vždy vyhodnotili, rozhodli o ní centrálně na jednom místě a výsledek převedli do kontextu služby.
Kdy varianta Accept‑headeru přestane fungovat?
V praxi existují tři typické body zlomu, které je třeba předem řešit:
- Proxy-Policies: Některé Reverse Proxies/WAF pravidla normalizují nebo filtrují Accept hlavičky. Pak vaše API tiše přejde na výchozí hodnotu. Řešení: explicitně prověřit pravidla proxy, případně uchýlit se k
X-Api-Version. - Client-Libraries: Některé HTTP klientské knihovny nastaví vlastní Accept hlavičky a přepíší hodnoty. Řešení: podporovat verzi kontraktu také jako volitelný query parametr (pouze jako fallback), nebo Accept hlavičku na serveru tolerantně parsovat.
Accept variieren (Vary: Accept), sonst liefert er Version 1 an Version-2-Clients. Lösung: Vary bewusst setzen, oder Caching auf API-Ebene deaktivieren.Úryvek zdrojového kódu: kontext požadavku, Correlation-ID, verze a mapování chyb
Kód je záměrně navržen tak, aby šel integrovat do existujících serverových projektů RemObjects: malá vrstva kontextu, parser pro verzi API (z hlavičky Accept), mechanismus Correlation-ID a centrální Exception-Mapping. Pojmy:
- Correlation-ID: Jedinečné ID pro požadavek, které se vrací v odpovědi a na které se odkazuje v logech.
- Exception-Mapping: Převod interních Delphi-výjimek na stabilní, klientem zpracovatelné objekty chyb (včetně HTTP stavu).
- Contract-Version: Verze JSON kontraktu, která řídí chování a pole.
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žadavku místo „někde v threadlocal“
Úryvek rozděluje záměrně: TApiContext je minimální stav, který chcete předat dál. V RemObjects SDK se mnoho řeší přes server-/channel-kontext. V heterogenních projektech (např. další worker-thready, DB-queue, background joby) je explicitní předávání často robustnější než implicitní threadlocaly, protože tím činíte souběžnost a přechody kontextu viditelnější.
Podmínky: Varianta s Accept headerem předpokládá, že váš reverse proxy (nginx, IIS ARR, Traefik) header beze změn předá dál. V některých prostředích jsou „nezvyklé“ Accept headery filtrovány nebo konsolidovány.
Úskalí: Versioning přes Accept je jen tak dobrý jako vaše testy. Pokud klienti používají knihovny, které Accept přepisují, může API náhle spadnout na výchozí verzi. Pro legacy klienty je výchozí fallback rozumný, musí být ale viditelný v monitoringu (např. log-varování „Version defaulted“).
Varianty: Pokud preferujete versioning přes X-Api-Version: parser je identický, liší se pouze zdroj hlavičky. Z pohledu gatewayů je to někdy snazší kontrolovat.
Integrace do RemObjects SDK: Correlation-ID a Exception-Mapping při vstupu služby
Skutečný efekt vzniká, když mechaniku konzistentně použijete na okraji serveru: jednou při vstupu requestu přečíst z headerů, jednou při výstupu z výjimky přeložit do stabilní response. Podle hostingu (např. RO-HTTP-Server, IIS-hosting, vlastně provozovaný Windows-/Windows- und Linux-Services) se liší konkrétní hook-body; princip je však stejný: postavit context, zavolat business logiku, centralně namapovat výjimky.
V RemObjects projektech se často pracuje přímo v každé servisní metodě. To zpočátku škáluje dobře, ale v provozu to selhává: každá metoda buduje logging a ošetření chyb jinak. Čisté rozhraní je service-base nebo dispatcher, který to standardizuje.
Praktický postup (záměrně stručný a implementačně orientovaný)
- Přečíst Correlation-ID z request-headeru
X-Correlation-ID; pokud chybí, vygenerovat na serveru (např. GUID). - Přečíst Contract-Version z
Accept(nebo zX-Api-Version). - Zalogovat start requestu: metoda, cesta, Correlation-ID, remote IP, spustit měření doby trvání.
- Spustit business logiku; DB přístupy co nejvíce zapouzdřit transakčně.
- Ošetřit výjimky: určit HTTP status, vytvořit JSON chybový objekt, nastavit response-header
X-Correlation-ID. - Zalogovat konec requestu: status, doba, případně chybový kód.
Threading na serveru: Proč je Correlation-ID bez kontextové disciplíny k ničemu
Častý Delphi okrajový případ: servisní metoda spustí asynchronní práci (např. generování reportu, import, push do DMS). V takovém případě už původní request-thread není ten, který později zapíše logy. Pokud je Correlation-ID známá pouze „na začátku“, ztrácí se sledovatelnost.
Pragmatické pravidlo: Vše, co neproběhne striktně v request-threadu, dostane context explicitně předaný. I když to vypadá jako více parametrů, vyplatí se to. Alternativou je práce s jasně definovaným kontextovým objektem, který je záměrně předán workerům (místo globálních proměnných nebo skrytých singletonů).
Typické bodu zvratu v RemObjects-/Delphi-servery:
- DB připojení na vlákno: BDE-Ablosung mit nativer Anbindung-připojení nejsou automaticky bezpečně sdílená mezi vlákny. Connection pool nebo jedno připojení na vlákno je často vhodnější než „jedno globální připojení“.
- Hranice transakcí: Pokud máte v rámci jednoho requestu několik kroků, které k sobě patří, musí transakce zůstat v téže logické jednotce. Asynchronní práce nesmí „omylem“ pokračovat v téže transakci.
- Zrušení: Když klient přeruší spojení (vypršení časového limitu proxy, zavřený prohlížeč), server často běží dál. Zvažte uvědoměle, zda má smysl tehdy spouštět pozadí práce.
Přístup k datům a chybové kódy: 409 není „také 500“
V integračních projektech je čisté mapování chyb víc než kosmetika. Rozhoduje o tom, zda protistrana (ERP‑connector, ETL‑job, zákaznické portál) dokáže správně reagovat. Několik praktických pravidel, která se osvědčila v Delphi/RemObjects-prostředích:
- 400 Bad Request: Validace, chybějící/neplatné parametry, JSON nelze parsovat. Důležité: odpověď by měla být stabilní i v případě poškozeného těla.
- 401/403: Rozlišujte autentizaci a autorizaci. 401 znamená „žádná/neplatná identita“, 403 „identita v pořádku, ale zakázáno“.
- 404: Zdroj neexistuje. Opatrnost z hlediska bezpečnosti: Ne vždy je vhodné prozrazovat, zda něco existuje.
- 409 Conflict: Obchodní konflikt (např. konflikt verzí, „stav neumožňuje tuto akci“, porušení unikátního klíče, pokud je to obchodně relevantní).
- 422 Unprocessable Content: Když je syntakticky vše v pořádku, ale selže obchodní validace (ne každý tým používá 422, ale často je srozumitelnější než 400).
- 500: Vše, co nelze jednoznačně zařadit. Sem patří i „DB down“, „Timeout“, „Unhandled Exception“.
Delphi-specifický trik: Mnoho DB chyb vystoupí jako generické výjimky. Vyplatí se v datové vrstvě cíleně rozpoznat známé situace a převést je do EApiError. Důležité přitom: nepřenášejte do zprávy klientovi SQL fragmenty ani interní názvy tabulek/sloupců. Tyto detaily patří do logu, ne do response.
Trik pro ladění: reprodukovatelné chyby pomocí „Contract Snapshot“
Neobvyklé, ale v provozu extrémně užitečné: při chybách (nebo cíleně u určitých Correlation‑ID) uložte „snapshot“ z hlaviček požadavku + těla požadavku do ladicího spool‑souboru. Není to trvalé logování (ochrana osobních údajů/objem), ale kontrolovaný nástroj pro reprodukci těžko opakovatelných případů z provozního prostředí.
Důležité: Snapshot nesmí nikdy nevyfiltrovaně persistovat Auth‑hlavičky, tokeny nebo osobní údaje. V praxi to znamená: redakce (maskování) a aktivace pouze přes feature‑flag nebo whitelist (např. pouze pro konkrétní Correlation‑ID, na krátká časová okna).
Čistá realizace v praxi: maskování místo vynechání
V reálných integracích jsou právě „kritická“ pole často ta, která byste k ladění potřebovali (např. identifikátory). Místo plošného vynechávání je lepší maskování: částečná náhrada tokenů, e‑mail ponechat jen s doménou, IBAN jen s posledními číslicemi. Tím zůstane případ reprodukovatelný, aniž by se zbytečně šířily citlivé údaje do filesystému. Navíc by měl být snapshot jasně označen jako ladicí artefakt a mít definovanou dobu uchování.
Bezpečnost a provoz: předávání hlaviček, řetězce proxy a timeouts
REST API obvykle nekončí přímo u klienta. Běžné jsou řetězce z Reverse Proxy, TLS-Termination, WAF nebo API-Gateway. Z toho vyplývají praktické body:
- Remote IP: Nespoléhejte se bezhlavě na
X-Forwarded-For. Přijímejte ho pouze od důvěryhodných proxy a jinak používejte přímou socket IP. V provozních příručkách by mělo být uvedeno, které hopy jsou „trusted“. - Timeouts: Pokud má proxy 30 sekund, ale vaše backendová služba potřebuje 2 minuty, generujete ghost-requests. Nastavte timeouts konzistentně podél celého řetězce a rozhodněte se: synchronní request nebo job-pattern (202 Accepted + statusový endpoint).
- Correlation-ID: Vkládejte Correlation-ID do response headerů, aby ji administrátoři mohli sloučit z logů a z klientské strany. Pokud gateway používá vlastní Request-IDs: logujte a mapujte obě ID.
- Chybové texty: V produkci žádné interní detaily. Debug detaily pouze řízeně (Stage/Feature-Flag) a v pochybnostech pouze v logu.
Zařazení: Proč může RemObjects SDK v tomhle mít výhodu
V Delphi-ekosystémech se REST-servery často staví s lehčími frameworky (např. minimalistické HTTP-routery). RemObjects SDK rozvine své přednosti, pokud už máte nebo potřebujete vícevrstvou architekturu:
- Jasné hranice služeb: Metody služby jsou explicitní, smluvní rozhraní jsou verziovatelné.
- Transporty a serializace: Můžete komunikovat v JSON, ale také v jiných formátech zpráv (podle nastavení), aniž byste zamíchali doménovou logiku.
- Provoz: Možnosti hostování a integrace do existujících Windows- a Linux-servisů jsou plánovatelné, včetně čistých nasazení.
Ukázaný přístup doplňuje části, které v praxi často chybí: jednotné chybové objekty, deterministická verzionace a korrelovatelné logování. Zejména u individuálního podnikového softwaru s dlouhým životním cyklem tím ušetříte čas při aktualizacích a při integraci externích systémů.
Závěr: Oplatí se úsilí — a kde přístup selhává?
Přidaná hodnota vzniká, pokud vaše REST rozhraní nejen „funguje“, ale je trvale provozuschopné: stabilní JSON smlouvy, verzionování bez URL-chaosu, sledovatelné chyby a debug bez hádání. Právě zde je přístup s Context, Correlation-ID a centrálním Exception-Mappingem v RemObjects SDK silný.
Mezní použití: Pokud máte jen jediný, krátkodobý endpoint bez integračních partnerů, působí Media-Type-verzionování rychle jako overengineering. I snapshot-logging dává smysl jen tehdy, když disciplinovaně implementujete redaction a aktivaci. A: pokud váš proxy-stack hlavičky „optimalizuje“ nebo odstraňuje, musíte nejprve upravit infrastrukturu, jinak budete debugovat nesprávnou vrstvu.
Pokud modernizujete existující Delphi-serverovou krajinu nebo čistě integrujete procesně blízké softwarové řešení do ERP/DMS/CRM, bývají právě tyto mechanismy často rozdílem mezi „běží v testu“ a „běží v provozu“.
V odborném prostředí hrají také Delphi REST-API a REST-server a Remobjects Sdk Delphi důležitou roli, když musí integrace, datové toky a další vývoj spolehlivě spolupracovat.
Další krok
Když se z tématu stane reálný projekt, měly by být architektura, stávající systém a provoz včas posuzovány společně.
Podporujeme nejen při jednotlivých otázkách, ale i v případě, že se z útržků zdrojového kódu, legacy témat nebo nápadů na portál má vyvinout robustní podnikový projekt.
- Současný stav, cílový stav a technická rizika jsou hodnoceny společně.
- REST, přístup k datům, portály a nasazení nebudou odkládány na později.
- Vidíte včas, která cesta je ekonomicky i provozně životaschopná.