A magazintémától a projektgyakorlatig
A bejegyzéshez tartozó szolgáltatási és technikai oldalak
Miért dől el a gyakorlatban gyakran a „REST API RemObjects SDK-val” a szélsőséges esetekben
Egy REST API RemObjects SDK-val ritkán esik szét a „Hello World”-szolgáltatásnál; a döntő pontok ott vannak, ahol az üzemeltetés, a legacy és az integráció találkozik: verziókezelés állásidő nélkül, konzisztens hibakezelés minden végponton, reprodukálható hibakeresés proxy-láncok esetén és a képeség egy kérés egyértelmű korrelálására probléma esetén.
A RemObjects SDK sok infraelemet ad ehhez: szolgáltatások, üzenetformátumok, serializáció, hosting (pl. mint Windows- és Linux-szolgáltatások vagy IIS/Reverse Proxy mögött) és definiált pontok a hibák központi kezelésére. Ami azonban a felhalmozódott üzleti szoftverlandscape-ekben gyakran hiányzik, az egy következetesen végigvitt szerződés: mely JSON-mezők stabilak? Hogyan jelezzük a hibákat? Hogyan ismerünk fel egy kérést újra, ha az Load Balancer-en, TLS-termináción és több backend rétegen áthaladt?
A következő megközelítés (beleértve egy Delphi-kódrészletet) egy robusztus irányvonalat mutat a RemObjects SDK-hoz: JSON-szerződések verziózása, a Correlation-ID (Request-ID a nyomon követéshez) kikényszerítése, Exceptions átképzése HTTP státuszokká és JSON-hibaobjektumokká, miközben a hibakeresés és az üzemeltetés érdekeit nem szembeállítjuk. Emellett megnézzük azokat a szélestestű eseteket, amelyek valódi környezetekben rendszeresen előfordulnak: szerveroldali threading, adatbázis-hozzáférések BDE-kiváltás natív illesztéssel, proxy-fejlécek, timeoutek és „szennyezett” kliens-payloadok.
Architektúra-döntés: verziózás média-típus (Media Type) alapján URL helyett
Sok API a verziót az olyan útvonalakon kezeli, mint a /v1/. Ez pragmatikus, de hosszabb távú integrációkban (pl. ERP/DMS/CRM kapcsolódások) gyakran URL-duplikációhoz, duplikált route-okhoz, duplikált tesztekhez és az „Melyik verziót használjuk valójában?“ kérdéshez vezet az üzemeltetési kézikönyvekben.
Egy alternatíva a verziózás a media type (content negotiation) segítségével. A kliens például küldheti: Accept: application/vnd.company.order+json;v=2. A szerver determinisztikusan kiolvassa a verziót és a contract/DTO viselkedést ennek megfelelően állítja be. Ez működik proxy- és cache-láncokban, ha a header-eket tisztán továbbítják. Az adminok számára is jól ellenőrizhető: egy request reprodukálható Curl/Postman-nel úgy, hogy az URL-ek nem változnak.
A RemObjects SDK nem „REST-purista“, hanem egy pragmatikus szolgáltatáskeret. Pont ezért érdemes a média-típus alapú megoldást alkalmazni: megtarthatók a stabil végpontok, miközben a szerződéseket tovább lehet fejleszteni. Fontos, hogy a verziót mindig kiértékeljék, egy központi ponton döntsék el és az eredményt átvigyék a szolgáltatás kontextusába.
Mikor bukik meg az Accept-header megközelítés?
Gyakorlatban három tipikus töréspont van, amelyet előre érdemes kezelni:
- Proxy-szabályok: Egyes reverse proxy-k/WAF szabályok normalizálják vagy megszűrik az Accept-header-t. Ilyenkor az API észrevétlenül visszaesik az alapértelmezett verzióra. Megoldás: ellenőrizze kifejezetten a proxy-szabályokat, szükség esetén térjen át
X-Api-Versionhasználatára. - Client-könyvtárak: Néhány HTTP-kliens saját Accept-header-t állít be és felülírja az értékeket. Megoldás: a contract-verziót támogassa opcionális query-paraméterként is (csak fallbackként), vagy a szerveroldalon legyen toleráns az Accept-header feldolgozása.
- Caching: Ha válaszok gyorsítótárazása (Response-Caching) aktív, a gyorsítótárnak az
Acceptfejléc alapján kell változnia (Vary: Accept), különben 1-es verziójú választ szolgál ki 2-es verziójú klienseknek. Megoldás:Varytudatos beállítása, vagy a gyorsítótárazás letiltása az API-szinten.
Forráskódrészlet: Request-Context, Correlation-ID, verzió és hibatérképezés
A kód szándékosan úgy van felépítve, hogy beilleszthető legyen meglévő RemObjects-szerverprojektekbe: egy kisebb Context-réteg, egy parser az API-verzióhoz (a Accept-ből), egy Correlation-ID-mechanizmus és egy központi kivétel-leképezés. Fogalmak:
- Correlation-ID: Egyedi azonosító kérésenként, amely megjelenik a válaszban és hivatkozható a naplókban.
- Exception-Mapping: Belső Delphi-kivétel(ek) átalakítása stabil, kliens által feldolgozható hibajobjektumokká (beleértve az HTTP státuszt).
- Contract-Version: A JSON-szerződés verziója, amely meghatározza a viselkedést és a mezőket.
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.Cél: stabil Request-kontextus a „irgendwo im Threadlocal” helyett
A kódrészlet szándékosan elválaszt: TApiContext az a minimális állapot, amit Ön továbbadni szeretne. A RemObjects SDK-ban sok minden a szerver-/csatorna-konteksten keresztül fut. Heterogén projektekben (pl. további worker-threadek, DB-queue, háttérfeladatok) a kontextus explicitt átadása gyakran robusztusabb, mint az implicit Threadlocal használata, mert így a párhuzamosság és a kontextusváltások jobban láthatóvá válnak.
Kerülendő feltételek: Az Accept-header alapú megoldás feltételezi, hogy a reverse proxy (nginx, IIS ARR, Traefik) a headert változtatás nélkül továbbítja. Egyes környezetekben a „szokatlan” Accept-headereket kiszűrik vagy egyesítik.
Veszélyforrások: Verziózás Accept-en keresztül csak olyan jó, mint a tesztjei. Ha a kliens könyvtárakat használ, amelyek felülírják az Accept-et, az API hirtelen a defaultra eshet vissza. Legacy kliens esetén logikus egy default-fallback, de ennek láthatónak kell lennie a monitoringban (pl. Log-figyelmeztetés: „Version defaulted”).
Variánsok: Ha a verziózást inkább a X-Api-Version headerrel végzi: a parser azonos, csak a forrása más header. Gateway-k szempontjából ez néha egyszerűbben kontrollálható.
Integráció a RemObjects SDK-val: Correlation-ID és Exception-Mapping a szolgáltatás belépési pontján
A tényleges hatás akkor jön létre, ha a mechanikát következetesen az Ön szervere szélén alkalmazza: egyszer a request belépésénél a headerekből kiolvasni, egyszer az exception kimeneténél stabil response-á fordítani. A hosting-tól függően (pl. RO-HTTP-Server, IIS-hosting, saját üzemeltetésű Windows-/Windows- és Linux-Services) a konkrét hook-pontok eltérnek; az elv ugyanaz: Context felépítése, business-logika meghívása, Exception-ök központi mappolása.
RemObjects-projektekben gyakori, hogy közvetlenül szolgáltatás-módszinten dolgoznak. Ez kezdetben jól skáláz, de üzem közben problémákhoz vezet: minden metódus másként építi fel a logolást és a hibakezelést. Egy tiszta határvonal egy service-alap vagy egy standardizált dispatcher, ami egységesen kezeli ezt.
Gyakorlati folyamat (tudatosan rövid és implementáció-közeli)
- Correlation-ID olvasása a Request-headerből
X-Correlation-ID; ha hiányzik, szerver-oldalon generálni (pl. GUID). - Contract-version olvasása az
Accept-ből (vagy aX-Api-Version-ból). - Request kezdete logolása: metódus, útvonal, Correlation-ID, távoli IP, időmérés indítása.
- Business-logika végrehajtása; adatbázis-hozzáféréseket lehetőleg tranzakciósan kapszulázni.
- Exception elkapása: HTTP státusz meghatározása, JSON-hibobjektum előállítása, Response-header
X-Correlation-IDbeállítása. - Request vége logolása: státusz, időtartam, esetleg hiba-kód.
Threading a szerveren: miért válik értelmetlenné a Correlation-ID kontextus-disciplína nélkül
Egy gyakori Delphi-végponti eset: a szolgáltatás-módszer aszinkron munkát indít (pl. riportgenerálás, import, push egy DMS-be). Ilyenkor az eredeti request-thread már nem az, amely később log-sorokat ír. Ha a Correlation-ID csak „az elején” ismert, a nyomonkövethetőség felbomlik.
Pragmatikus szabály: Minden, ami nem marad szigorúan a request-threaden, kapja meg a Context expliciten átadva. Bár ez hosszabb paraméterlistákat eredményezhet, megtérül. Alternatívaként egy jól definiált kontextus-objektummal dolgozhatunk, amelyet tudatosan átadnak a workereknek (a globális változók vagy rejtett singletonok helyett).
Tipikus töréspontok RemObjects-/Delphi-szervereken:
- Szálankénti DB-kapcsolatok: BDE-Ablosung mit nativer Anbindung-kapcsolatok nem oszthatók meg automatikusan thread-biztos módon. Egy Connection-pool vagy szálanként egy kapcsolat gyakran célszerűbb, mint „egy globális kapcsolat”.
- Tranzakciós határok: Ha egy kérésen belül több, egymáshoz tartozó lépés van, a tranzakciónak ugyanabban a logikai egységben kell maradnia. Aszinkron munka nem folytatódhat „véletlenül” ugyanabban a tranzakcióban.
- Cancellation: Ha a kliens megszakítja a kapcsolatot (Proxy timeout, Browser closed), a szerver gyakran továbbfut. Mérlegelje tudatosan, van-e értelme ilyenkor a háttérmunkának.
Adat-hozzáférés és hibakódok: 409 ist nicht „auch ein 500”
Az integrációs projektekben a tiszta hibamapping több mint kozmetika. Eldönti, hogy egy partner (ERP-Connector, ETL-Job, Ügyfélportál) helyesen tud-e reagálni. Néhány gyakorlatias iránymutatás, amelyek beváltak Delphi/RemObjects környezetekben:
- 400 Bad Request: Érvényesítés, hiányzó/érvénytelen paraméterek, JSON nem feldolgozható. Fontos: a válasznak stabilnak kell maradnia akkor is, ha a body sérült.
- 401/403: Hitelesítést és jogosultságot külön kell választani. A 401 jelentése „nincs/érvénytelen identitás”, a 403 „identitás rendben, de tiltott”.
- 404: Az erőforrás nem létezik. Biztonsági megfontolások: nem mindig szabad felfedni, hogy valami létezik-e.
- 409 Conflict: Üzleti konfliktus (pl. verzióütközés, „a státusz nem engedélyezi ezt a műveletet”, egyedi kulcs megsértése, ha az üzletileg releváns).
- 422 Unprocessable Content: Ha szintaktikailag minden rendben van, de üzleti érvényesítés sikertelen (nem minden csapat használja a 422-t, de gyakran egyértelműbb, mint a 400).
- 500: Minden, amit nem tud megfelelően osztályozni. Ide tartozik a „DB down”, „Timeout”, „Unhandled Exception” is.
Delphi-specifikus trükk: Sok DB-hiba generikus kivételként érkezik fel. Megéri az adat-hozzáférési rétegben célzottan felismerni ismert helyzeteket és azokat EApiError-be átvinni. Fontos: Ne adjon át SQL-töredékeket vagy belső táblák-/oszlopok neveit a kliens üzenetében. Ezek a részletek a logba valók, nem a válaszba.
Debugging-trükk: reprodukálható hibák „Contract Snapshot” segítségével
Szokatlan, de üzemeltetésben rendkívül hasznos: Hibák esetén (vagy célzottan bizonyos Correlation-ID-knél) mentsen el egy „Snapshot”-ot a Request-headerekből + Request-body-ból egy debug spool fájlba. Ez nem állandó logolás (adatvédelmi/terjedelmi okok), hanem egy kontrollált eszköz a nehezen reprodukálható esetek éles közeli utánozásához.
Fontos: Egy snapshot soha nem tárolhat szűretlenül Auth-headereket, tokeneket vagy személyes adatokat. A gyakorlatban ez redactionot (maszkolást) és csak Feature-Flag vagy whitelist általi aktiválást jelent (pl. csak bizonyos Correlation-ID-kre, rövid időablakokra).
Gyakorlati, tiszta megvalósítás: Maszkolás a kihagyás helyett
Valódi integrációkban gyakran éppen a „kritikus” mezők azok, amelyekre a hibakereséshez szükség van (pl. azonosítók). Az általános kihagyás helyett jobb a maszkolás: a tokenek részleges helyettesítése, az e-mail címből csak a domain megtartása, az IBAN-ból csak az utolsó számjegyek megtartása. Így a hiba reprodukálható marad anélkül, hogy felesleges adatok terjednének a fájlrendszerben. Emellett a snapshotot egyértelműen debug-artefaktumként kell megjelölni, és meghatározott megőrzési időt kell neki adni.
Biztonság és üzemeltetés: fejléc-továbbítás, proxy-láncok és időkorlátok
Egy REST API ritkán ér véget közvetlenül a kliensnél. Tipikusak a reverse proxy, TLS-terminálás, WAF vagy API-gateway láncok. Ebből gyakorlati szempontok adódnak:
- Távoli IP: Ne támaszkodjon vakon a
X-Forwarded-For-ra. Csak megbízható proxyktól vegye át; különben használja a közvetlen socket-IP-t. Az üzemeltetési kézikönyvben legyen rögzítve, mely hopok tekinthetők megbízhatónak. - Időkorlátok: Ha a proxy 30 másodperces határidőt tart, az Ön backendje viszont 2 percet igényel, fantomkéréseket (ghost-requests) generál. Állítsa be következetesen az időkorlátokat a lánc mentén, és döntse el: szinkron kérést kezel, vagy job-patternt alkalmaz (202 Accepted + státusz-végpont).
- Correlation-ID: Tegye be a Correlation-ID-t a válaszfejlécekbe, hogy az adminok a naplók és a kliensoldal adatai alapján összekapcsolhassák a műveleteket. Ha egy gateway saját Request-ID-ket használ, mindkét azonosítót logolja és ábrázolja.
- Hibaüzenetek: Éles üzemben ne adjon ki belső részleteket. Hibakeresési részletek csak kontrollált környezetben legyenek elérhetők (stage/feature-flag), és szükség esetén csak a naplóban legyenek megtalálhatók.
Elhelyezés: Miért lehet a RemObjects SDK ebben előnyös
A Delphi-ökoszisztémában a REST-szervereket gyakran könnyebb keretrendszerekkel (pl. minimalista HTTP-routerek) építik. A RemObjects SDK akkor játssza ki erősségét, ha már meglévő vagy szükséges egy többrétegű architektúra:
- Egyértelmű szolgáltatási határok: A szolgáltatásmetódusok explicit módon definiáltak, a kontraktusok verziózhatók.
- Transzportok és szerializáció: Beszélhet JSON-nal, de szükség szerint más üzenetformátumokat is használhat, anélkül hogy a domainlogikát összekeverné.
- Üzemeltetés: A hoszting-opciók és a meglévő Windows- és Linux-szolgáltatások integrációja tervezhető, beleértve a szabályozott bevezetéseket.
A bemutatott megközelítés kiegészíti ezeket azokkal az elemekkel, amelyek a mindennapi gyakorlatban gyakran hiányoznak: egységes hibaobjektumok, determinisztikus verziózás és korrelálható naplózás. Különösen egyedi vállalati szoftvereknél, ahol hosszú az életciklus, ezzel időt takaríthat meg frissítéseknél és külső rendszerek integrálásánál.
Következtetés: Megéri az erőfeszítés — és hol válik problémássá a megközelítés?
Az érték akkor keletkezik, ha az Ön REST-felülete nem csak „működik”, hanem hosszú távon üzemeltethető: stabil JSON-szerződések, verziózás URL-szaporodás nélkül, nyomon követhető hibák és találgatás nélküli hibakeresés. Pont itt erős a megközelítés a Context, Correlation-ID és a központi Exception-Mapping alkalmazásával a RemObjects SDK-ban.
Alkalmazási korlátok: Ha csak egyetlen, rövid életű végpontja van integrációs partnerek nélkül, a Media-Type-verziózás könnyen túlkomplikálttá válhat. A snapshot-naplózás szintén csak akkor indokolt, ha a Redaction és az aktiválás fegyelmezetten van megvalósítva. És: ha a proxy-stack a fejléceket „optimalizálja” vagy eltávolítja, először az infrastruktúrát kell rendbe hoznia, különben a rossz réteget fogja hibakeresni.
Ha meglévő Delphi-szerverparkot modernizál, vagy egy folyamatközeli szoftvermegoldást tisztán kell integrálnia ERP/DMS/CRM rendszerekbe, ezek a mechanizmusok gyakran jelentik a különbséget a „teszten fut” és az „üzemben fut” állapot között.
A szakmai környezetben továbbá fontos szerepet játszanak a Delphi REST-API és REST-szerver és a Remobjects Sdk Delphi, amikor az integrációknak, az adatfolyamoknak és a további fejlesztésnek zavartalanul együtt kell működniük.
Projektet vagy modernizációs tervet egyeztessen a Net-Base-szel.
Következő lépés
Ha egy témából valós projekt lesz, az architektúrát, a meglévő rendszert és az üzemeltetést korai fázisban együtt kell vizsgálni.
Nemcsak egyedi kérdésekben támogatunk, hanem akkor is, amikor forráskódrészletekből, örökölt rendszerekkel kapcsolatos témákból vagy portálötletekből robusztus vállalati projektet kell kialakítani.
- A jelenlegi állapotot, a célállapotot és a műszaki kockázatokat együttesen értékeljük.
- REST, az adathozzáférést, a portálokat és a bevezetést nem halasztjuk későbbi fázisokra.
- Ön korán látja, melyik út gazdaságilag és üzemeltetési szempontból tartható.