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 robovih
„Hello World“ servis redko razveljavi uspeh REST API z RemObjects SDK; odločilne točke so tam, kjer se srečujejo obratovanje, legacy in integracija: verzioniranje brez zaustavitve, konsistentno obnašanje ob napakah na vseh endpointih, reproducibilno razhroščevanje v verigah proxyjev in sposobnost enolične korelacije zahtevkov v primeru težav.
RemObjects SDK ponuja za to obsežno infrastrukturo: servise, formate sporočil, serializacijo, hosting (npr. kot Windows- in Linux-servisi ali za IIS/reverse proxyjem) in definirane točke za centralno obravnavo napak. V zrastlih poslovnih softverskih okoljih pa pogosto manjka dosledno uveden dogovor: Katera JSON-polja so stabilna? Kako signaliziramo napake? Kako znova identificiramo zahtevek, ko je prešel skozi load balancer, TLS-terminacijo in več plasti backendov?
Naslednji pristop (vključno z Delphi-izrezki) prikazuje robustno linijo za RemObjects SDK: verzioniranje JSON-pogodb, Correlation-ID (ID zahtevka za sledenje) obvezno, preslikava Exceptions v HTTP-status in JSON-objekte napak ter hkratna podpora za učinkovito razhroščevanje in stabilno obratovanje. Dodatno obravnavamo obrobne primere, ki se v realnih okoljih redno pojavijo: upravljanje niti na strežniku, dostopi do podatkovnih baz pri BDE-zamenjavi z nativno povezavo, proxy-headerji, časovne omejitve (timeouts) in „onesnaženi“ odjemalski payloadi.
Arhitekturna odločitev: verzioniranje preko medijskega tipa namesto URL
Veliko API-jev verzionira preko poti, kot je /v1/. To je praktično, a pri dalj časa trajajočih integracijah (npr. povezave ERP/DMS/CRM) pogosto vodi v dupliciranje URL-jev, podvajanje rout, dvojne teste in vprašanja „Katero verzijo sploh uporabljamo?“ v dokumentaciji za obratovanje.
Alternativa je verzioniranje preko Media Type (Content Negotiation). Klient pošlje npr. Accept: application/vnd.company.order+json;v=2. Strežnik deterministično prebere različico in prilagodi vedenje contract/DTO. Deluje v verigah proxyjev in predpomnilnikov, če se headerji pravilno posredujejo. Za administratorje je tudi preverljivo: zahtevek je mogoče reproducirati z Curl/Postman brez spreminjanja URL-jev.
RemObjects SDK ni „REST-purističen“, temveč pragmatično service-framework. Ravno zato se varianta z medijskim tipom splača: obdržite stabilne endpoint-e in hkrati razvijate pogodbe. Pomembno je, da različico vedno izpeljete, odločitev sprejmete centralno in rezultat vstavite v kontekst vašega servisa.
Kdaj odpove varianta z Accept-headerjem?
V praksi so tri tipične ranljivosti, ki jih je pametno nasloviti vnaprej:
- Proxy-politike: Nekateri reverse proxyji ali WAF-pravila normalizirajo ali filtrirajo Accept-header. V tem primeru vaša API tiho pade na privzeto različico. Rešitev: eksplicitno preverite nastavitve proxyja, po potrebi uporabite
X-Api-Versionkot izhodno možnost. - Knjižnice odjemalcev: Nekateri HTTP-klienti nastavijo svoje Accept-headerje in prepišejo vrednosti. Rešitev: podporo za verzijo pogodbe uvesti tudi kot neobvezen query-parameter (samo kot fallback) ali Accept-header na strežni strani tolerantno parsirati.
Accept (Vary: Accept), sicer bo dostavil različico 1 odjemalcem različice 2. Rešitev: Vary namerno nastaviti ali onemogočiti predpomnjenje na ravni API-ja.Izrezek iz vira: Request-kontekst, Correlation-ID, različica in mapiranje napak
Koda je namenoma zasnovana tako, da se lahko integrira v obstoječe RemObjects-strežniške projekte: majhen sloj konteksta, parser za različico API (iz Accept), mehanizem Correlation-ID in centralna preslikava izjem. Pojmi:
- Correlation-ID: Edinstvena ID za vsak zahtevek, ki se pojavi tudi v odzivu in je referencirana v dnevnikih.
- Exception-Mapping: Prevod notranjih Delphi-izjem v stabilne, za odjemalca obdelljive objekte napak (vključno z HTTP-statusom).
- Contract-Version: Različica JSON-pogodbe, ki določa 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: Stabilen kontekst zahteve namesto „nekje v Threadlocal“
Ta izsek jasno loči: TApiContext je minimalno stanje, ki ga želite prenesti. V RemObjects SDK veliko poteka prek strežniškega/kanalnega konteksta. V heterogenih projektih (npr. dodatne delovne niti, DB-Queue, ozadna opravila) je eksplicitno prenašanje pogosto bolj robustno kot implicitni threadlocali, ker z njim naredite vzporednost in menjave konteksta bolj vidne.
Pogoji: Accept-Header-Variante predpostavlja, da vaš Reverse Proxy (nginx, IIS ARR, Traefik) header prenese nespremenjen. V nekaterih okoljih se „nenavadni“ Accept-Header filtrirajo ali združijo.
Pasti: Versionierung über Accept je toliko dobra, kot so vaši testi. Če odjemalci uporabljajo knjižnice, ki prepišejo Accept, se lahko API nenadoma vrne na privzeto. Za Legacy-Clients je ein Default-Fallback smiselna, vendar mora biti to v monitoringu vidno (npr. opozorilo v logu „Version defaulted“).
Varianten: Če želite različičenje raje preko X-Api-Version: parser je enak, le vir je drug header. Z vidika gatewayev je to včasih lažje nadzirati.
Integracija v RemObjects SDK: Correlation-ID in Exception-Mapping ob vstopu v storitev
Dejanski učinek nastopi, če mehaniko dosledno uporabite na robu vašega strežnika: enkrat na vstopu zahteve preberete iz headerjev, enkrat na izhodu izjeme prevedete v stabilen response. Glede na hosting (npr. RO-HTTP-Server, IIS-Hosting, samostojno upravljan Windows-/Windows- in Linux-Services) se konkretne točke priklopa razlikujejo; načelo ostaja enako: zgradite kontekst, pokličite poslovno logiko, izjeme centralno mappajte.
V RemObjects-projektih se pogosto dela neposredno po metodi storitve. To sprva dobro skalira, vendar pri obratovanju odpove: vsaka metoda gradi logiranje in obravnavo napak drugače. Čist razrez je Service-Basis ali Dispatcher, ki standardizira.
Praktični potek (namenoma kratek in implementacijsko blizu)
- Correlation-ID iz Request-Header
X-Correlation-IDpreberite; če manjka, ustvarite na strežniški strani (npr. GUID). - Različico pogodbe preberite iz
Accept(ali izX-Api-Version). - Začetek zahteve zabeležite v log: metoda, pot, Correlation-ID, oddaljeni IP, začnite merjenje trajanja.
- Izvedite poslovno logiko; dostop do DB po možnosti kapsulirajte v transakcije.
- Ujemite izjemo: določite HTTP-status, ustvarite JSON-objekt napake, nastavite Response-Header
X-Correlation-ID. - Konec zahteve zabeležite v log: status, trajanje, po potrebi kodo napake.
Threading im Server: Warum Correlation-ID ohne Kontext-Disziplin wertlos wird
Eden pogostih Delphi-robnih primerov: metoda storitve sproži asinhrono delo (npr. generiranje poročil, uvoz, push v DMS). Takrat izvorna nit zahteve ni več tista, ki kasneje zapisuje vrstice v log. Če je Correlation-ID poznana le „na začetku“, sledljivost razpade.
Pragmatično pravilo: Vse, kar ne ostane strogo v nitki zahteve, naj prejme kontekst eksplicitno predan. Čeprav to zgleda kot daljši seznam parametrov, se izplača. Kot alternativa lahko uporabljate jasno definiran kontekstni objekt, ki ga zavestno predajate workerjem (namesto globalnih spremenljivk ali skritih singletonov).
Tipične kipnote v RemObjects-/Delphi-serverjih:
- DB-Connections na nit: BDE-Ablosung mit nativer Anbindung-povezave niso samodejno varno deljive med nitmi. Connection-pool ali ena povezava na nit je pogosto smiselnejša kot „eine globale Connection“.
- Meje transakcij: Če imate znotraj enega requesta več korakov, ki sodijo skupaj, mora transakcija ostati v isti logični enoti. Asinhrono delo ne sme „po nesreči“ teči naprej v isti transakciji.
- Preklic: Če klient prekine (proxy timeout, brskalnik zaprt), server pogosto nadaljuje. Premislite, ali ima delo v ozadju še smisel.
Dostop do podatkov in statusne kode: 409 ni „tudi 500“
V integracijskih projektih je urejeno preslikavanje napak več kot kozmetika. Odloča, ali nasprotna stran (ERP-Connector, ETL-Job, portal za stranke) lahko pravilno reagira. Nekaj praktičnih smernic, ki so se v Delphi/RemObjects-okoljih izkazale:
- 400 Bad Request: Validacija, manjkajoči/neveljavni parametri, JSON ni mogoče razčleniti. Pomembno: Odgovor naj ostane stabilen, tudi če je telo poškodovano.
- 401/403: Ločite avtentikacijo in avtorizacijo. 401 pomeni „ni/neuraden identiteta“, 403 pomeni „identiteta ok, a prepovedano“.
- 404: Vir ne obstaja. Previdnost pri varnosti: ne razkrivajte vedno, ali nekaj obstaja.
- 409 Conflict: Strokovni konflikt (npr. konflikt verzij, „stanje ne dovoljuje te akcije“, kršitev unikatnega ključa, če je to poslovno relevantno).
- 422 Unprocessable Content: Če je sintaktično vse v redu, a neuspešna poslovna validacija (ne vsaka ekipa uporablja 422, vendar je pogosto jasneje kot 400).
- 500: Vse, česar ne znate jasno klasificirati. Sem spada tudi „DB down“, „Timeout“, „Unhandled Exception“.
Delphi-specifičen prijem: Veliko DB-napak se pojavi kot generične izjeme. Izplača se na plasti za dostop 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 odziv.
Trik za odpravljanje napak: reproducibilne napake s „Contract Snapshot“
Neobičajno, a v obratovanju izjemno koristno: Ob napakah (ali namensko za določene Correlation-ID-je) shranite „snapshot“ iz Request-Headerjev + Request-Body v debug-spool datoteko. To ni trajno beleženje (varstvo podatkov/volumen), temveč kontrolirano orodje za reproduciranje težko reproducibilnih primerov v bližini produkcije.
Pomembno: Snapshot nikoli ne sme nefiltrirano trajno shranjevati Auth-Headerjev, tokenov ali osebnih podatkov. V praksi to pomeni: Redaction (maskiranje) in aktivacija samo preko feature-flaga ali bele liste (npr. le za določene Correlation-ID-je, kratka časovna okna).
Čista izvedba v praksi: maskiranje namesto izpuščanja
V resničnih integracijah so prav „kritična“ polja pogosto tista, ki jih potrebujete za debuganje (npr. identifikatorji). Namesto splošnega izpuščanja je bolje maskiranje: delno zamenjati tokene, pri e‑pošti obdržati samo domeno, pri IBAN‑u le zadnje številke. Tako ostane primer reproducibilen, brez razširjanja nepotrebnih podatkov v datotečnem sistemu. Poleg tega naj bo snapshot jasno označen kot debug-artefakt in naj ima določeno obdobje hrambe.
Varnost in obratovanje: posredovanje headerjev, verige proxyjev in timeouti
Eine REST API endet selten direkt am Client. Typisch sind Ketten aus Reverse Proxy, TLS-Termination, WAF oder API-Gateway. Daraus ergeben sich praktische Punkte:
- Remote IP: Ne zanašajte se slepo na
X-Forwarded-For. Prevzemajte ga le od zaupanja vrednih proxyjev; sicer uporabite neposredno socket-IP. V operativnih priročnikih naj bo jasno navedeno, kateri hopi so zaupanja vredni. - Timeouts: Če ima proxy nastavljeno 30 sekund, vaše backend-strežnik pa potrebuje 2 minuti, boste povzročili ghost-requests. Določite timeoute dosledno vzdolž celotne verige in se odločite za enotno obnašanje: sinhroni request ali job-pattern (202 Accepted + statusni endpoint).
- Correlation-ID: Nastavite Correlation-ID v odzivnih headerjih, da jo upravljavci lahko povežejo med logi in na strani klienta. Če gateway uporablja lastne request-ID, zabeležite in povežite obe ID v logih in pri sledenju.
- Fehlertexte: V produkcijskem obratovanju brez internih podrobnosti. Debug-podatke izdajajte le nadzorovano (stage/feature-flag) in v dvomu pustite podrobnosti v logu.
Einordnung: Warum RemObjects SDK hier im Vorteil sein kann
In Delphi-Ökosystemen werden REST-Server oft mit leichteren Frameworks (z. B. minimalistische HTTP-Router) gebaut. RemObjects SDK spielt seine Stärke aus, wenn Sie bereits eine mehrschichtige Architektur haben oder brauchen:
- Klare Service-Grenzen: Service-Methoden sind explizit, Contracts sind versionierbar.
- Transporte und Serialisierung: Sie können JSON sprechen, aber auch andere Message-Formate (je nach Setup), ohne die Fachlogik zu verquirlen.
- Betrieb: Hosting-Optionen und Integration in bestehende Windows- und Linux-Services sind planbar, inklusive sauberer Rollouts.
Der gezeigte Ansatz ergänzt das um die Teile, die im Alltag oft fehlen: einheitliche Fehlerobjekte, deterministische Versionierung und korrelierbares Logging. Gerade bei individueller Unternehmenssoftware mit langen Lebenszyklen sparen Sie damit Zeit bei Updates und bei der Integration externer Systeme.
Fazit: Lohnt sich der Aufwand – und wo kippt der Ansatz?
Der Mehrwert entsteht, wenn Ihre REST-Schnittstelle nicht nur „funktioniert“, sondern dauerhaft betreibbar ist: stabile JSON-Verträge, Versionierung ohne URL-Wildwuchs, nachvollziehbare Fehler und Debugging ohne Ratespiel. Genau dort ist der Ansatz mit kontekstom, Correlation-ID und zentralnim preslikavanjem izjem v RemObjects SDK stark.
Einsatzgrenzen: Če imate samo en sam, kratkotrajni endpoint brez integracijskih partnerjev, se Media-Type-Versionierung hitro prevrže v overengineering. Tudi Snapshot-Logging ima smisel le, če dosledno izvedete redaction in aktivacijo. In: če vaš proxy-stack headerje „optimiert“ ali odstrani, morate najprej urediti infrastrukturo, sicer boste odpravljali napake na napačnem nivoju.
Wenn Sie eine bestehende Delphi-Serverlandschaft modernisieren oder eine prozessnahe Softwarelösung sauber in ERP/DMS/CRM integrieren müssen, sind genau diese Mechanismen aber häufig der Unterschied zwischen „läuft im Test“ und „läuft im Betrieb“.
V strokovnem okolju igrajo prav tako pomembno vlogo Delphi REST-API in REST-Server in Remobjects Sdk Delphi, ko morajo integracije, podatkovni tokovi in nadaljnji razvoj delovati usklajeno.
O projektu ali modernizacijskem načrtu se pogovorite z Net-Base.
Naslednji korak
Ko se tema spremeni v dejanski projekt, je treba arhitekturo, obstoječi sistem 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 uvedba niso prestavljeni kot poznejše posledice.
- Zgodaj prepoznate, katera pot je ekonomsko in obratovalno vzdržna.