Net-Base Revija

09.06.2026

REST API z RemObjects SDK: dosledno verzioniranje in odpravljanje napak JSON-končnih točk (Delphi izsek izvorne kode)

Kako z RemObjects SDK v Delphi zgraditi REST API, ki med obratovanjem ne razpada: stabilne JSON-pogodbe, verzioniranje brez razraščanja URL-jev, Correlation-ID skozi vse plasti, centralno mapiranje napak, zapisovanje snapshotov za zahtevne primere odpravljanja napak ter praktični napotki...

09.06.2026

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 robnih primerih

En REST API z RemObjects SDK redko stoji ali pade na „Hello World“-storitev, temveč na mestih, kjer se srečujeta obratovanje, legacy in integracija: verzioniranje brez zaustavitve, dosledno obnašanje napak na vseh endpointih, reproducirljivo razhroščevanje pri verigah proxyjev in sposobnost enoznačnega koreliranja requestov v primeru težav.

RemObjects SDK prinaša veliko infrastrukture: storitve, formate sporočil, serializacijo, hosting (npr. kot Windows- in Linux-storitev ali za IIS/Reverse Proxy) ter definirane točke za centralno obravnavo napak. Kar pa v utrjenih poslovnih programskih okoljih pogosto manjka, je konseventno vpeljana pogodba: Katera JSON-polja so stabilna? Kako signaliziramo napake? Kako request znova prepoznamo, ko je šel skozi load balancer, TLS-terminacijo in več back-end plasti?

Slednji pristop (vključno z Delphi-izrezki) pokaže robustno linijo za RemObjects SDK: JSON-pogodbe verzionirati, Correlation-ID (Request-ID za sledenje) uveljaviti, Exceptions v HTTP-status in JSON-objekte napak preslikati ter hkrati ne postavljati razhroščevanja in obratovanja v nasprotje. Dodatno obravnavamo robne primere, ki se v realnih okoljih redno pojavljajo: threading na strežniku, dostopi do podatkovnih baz z BDE-odpravo z nativno vezavo, proxy-headerji, timeouti in „umažene“ klientske payload-e.

Arhitekturna odločitev: verzioniranje preko medijskega tipa namesto URL

Številne API-je se verzionira preko poti, npr. /v1/. To je pragmatično, vendar pri dolgotrajnih integracijah (npr. povezave ERP/DMS/CRM) pogosto vodi do podvajanja URL-jev, dvojnih rout, dvojnih testov in nejasnosti »katere verzije v resnici uporabljamo?« v operativni dokumentaciji.

Alternativa je verzioniranje preko medijskega tipa (pogajanje o vsebini). Klient pošlje npr. Accept: application/vnd.company.order+json;v=2. Strežnik deterministično prebere verzijo in prilagodi vedenje contracta/DTO. To deluje v verigah proxyjev in cache-jev, če se headerji pravilno posredujejo. Za skrbnike je dodatna prednost, da je preverljivo: request lahko reproducirate z Curl/Postman brez razlik v URL-jih.

RemObjects SDK ni „REST-purističen“, temveč pragmatično service-framework. Ravno zato se medijski-tip pristop izplača: ohranite lahko stabilne endpoint-e in hkrati razvijate pogodbe. Pomembno je, da različico vedno preberete, odločitev sprejmete centralno in rezultat prenesete v kontekst vaše storitve.

Kdaj odpove varianta z Accept-headerjem?

V praksi obstajajo trije tipični prelomi, ki jih je smiselno nasloviti vnaprej:

  • Proxy-politike: Nekateri reverse proxyji/WAF-pravila normalizirajo ali filtrirajo Accept-header. V tem primeru vaša API tiho preide na privzeto. Rešitev: izrecno preverite proxy-pravila, po potrebi uporabite X-Api-Version kot rezervno možnost.
  • Klientske knjižnice: Nekateri HTTP-klienti nastavijo svoje Accept-headerje in prepišejo vrednosti. Rešitev: podprite verzijo contracta tudi kot opcijski query-parameter (samo kot fallback) ali na strežni strani tolerantno parsirajte Accept-header.
  • Caching: Če se uporablja predpomnjenje odgovorov, mora predpomnilnik razlikovati po glavi Accept (Vary: Accept), sicer bo pošiljal različico 1 klientom, ki zahtevajo različico 2. Rešitev: namerno nastaviti Vary ali onemogočiti predpomnjenje na ravni API.

Izsek iz izvorne kode: Request-Context, Correlation-ID, različica in preslikava napak

Koda je namenoma oblikovana tako, da se jo lahko vključi v obstoječe RemObjects-server projekte: majhen sloj konteksta, parser za verzijo API (iz Accept), mehanizem Correlation-ID in centralno Exception-Mapping. Pojmi:

  • Correlation-ID: Enoličen ID za vsak zahtevek, ki se pojavi tudi v odzivu in po katerem se sklicujejo v logih.
  • Exception-Mapping: Prevod notranjih Delphi-Exceptions v stabilne, s klientom obdelljive objekte napak (vključno z HTTP-statusom).
  • Contract-Version: Različica JSON-pogodbe, ki krmili vedenje in polja.
Delphi
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: Stalen kontekst zahteve namesto „nekje v threadlocal“

Izsek namerno loči: TApiContext je minimalno stanje, ki ga želite predajati naprej. V RemObjects SDK se veliko dogaja preko konteksta strežnika/kanala. V heterogenih projektih (npr. dodatni worker-threadi, DB-queue, ozadni opravki) je eksplicitno predajanje pogosto bolj robustno kot implicitni threadlocali, saj z njim naredite sočasnost in zamenjave konteksta bolj vidne.

Omejitve: Varianta z Accept-headerjem predpostavlja, da vaš reverse proxy (nginx, IIS ARR, Traefik) header prenese nespremenjen. V nekaterih okoljih so „nenavadni“ Accept-headerji filtrirani ali združeni.

Pastke: Verzija preko Accept je dobra toliko, kolikor so vaši testi. Če klienti uporabljajo knjižnice, ki prepišejo Accept, se lahko API nenadoma vrne na privzeto. Za legacy-kliente je smiselno imeti privzeto vračilo, vendar mora biti to vidno v monitoringu (npr. log-opozorilo „Version defaulted“).

Variant: Če raje delate verzioniranje preko X-Api-Version: parser je identičen, le vir je drug header. Z vidika gatewayev je to včasih lažje nadzorovati.

Integracija v RemObjects SDK: Correlation-ID in exception-mapping ob vstopu v servis

Pravi učinek nastane, če mehaniko konsekventno uporabite na robu vašega strežnika: enkrat ob vstopu zahteve preberete iz headerjev, enkrat ob izhodu izjeme prevedete v stabilen odgovor. Glede na hosting (npr. RO-HTTP-Server, IIS-hosting, samostojno upravljani Windows-/Windows- in Linux-Services) se konkretne točke za hook razlikujejo; načelo pa ostaja enako: zgraditi kontekst, klicati poslovno logiko, izjeme centralno preslikati.

V RemObjects-projektih se pogosto dela neposredno na nivoju posamezne service-metode. To sprva dobro skalira, vendar pri obratovanju odpove: vsaka metoda gradi logging in obravnavo napak drugače. Čista ločnica je osnova servisa ali dispatcher, ki standardizira.

Praktični potek (namenoma kratek in implementacijsko naravnan)

  1. Preberite Correlation-ID iz request-headerja X-Correlation-ID; če manjka, ga strežnik ustvari (npr. GUID).
  2. Preberite verzijo contracta iz Accept (ali iz X-Api-Version).
  3. Zabeležite začetek requesta: metoda, pot, Correlation-ID, oddaljeni IP, začnite merjenje trajanja.
  4. Izvedite poslovno logiko; dostope do DB po možnosti zapakirajte transakcijsko.
  5. Ujemite izjemo: določite HTTP-status, ustvarite JSON-objekt napake, nastavite response-header X-Correlation-ID.
  6. Zabeležite konec requesta: status, trajanje, po potrebi šifro napake.

Threading na strežniku: Zakaj je Correlation-ID brez discipline konteksta brez vrednosti

Hitro pojavni Delphi-robni primer: servisna metoda sproži asinhrono delo (npr. generiranje poročil, import, push v DMS). Tedaj začetni request-thread ni več tisti, ki kasneje piše log-vrstice. Če je Correlation-ID znana le „na začetku“, sledljivost razpade.

Pragmatično pravilo: vse, kar ne ostane striktno v request-threadu, dobi kontekst eksplicitno predan. Tudi če to pomeni več seznamov parametrov, se izplača. Alternativno lahko delate s jasno definiranim objektom konteksta, ki ga namensko predate workerjem (namesto globalnih spremenljivk ali skritih singletonov).

Tipični prelomni trenutki v RemObjects-/Delphi-strežnikih:

  • DB-povezave na nit: BDE-Ablosung mit nativer Anbindung-povezave niso avtomatsko varne za sočasno rabo v več nitih. Connection-Pool ali ena povezava na nit je pogosto smiselnejša kot „globalna povezava“.
  • Meje transakcij: Če imate znotraj enega requesta več korakov, ki spadajo skupaj, mora transakcija ostati znotraj iste logične enote. Asinhrono delo ne sme „po naključju“ teči naprej v isti transakciji.
  • Preklic (Cancellation): Če klient prekine (proxy timeout, brskalnik zaprt), strežnik pogosto teče dalje. Premislite, ali ima ozadinsko delo v takšnem primeru še smisel.

Dostop do podatkov in kode napak: 409 ni „tudi 500“

V integracijskih projektih je dosledno preslikavanje napak več kot kozmetika. Odloča, ali lahko nasprotna stran (ERP-Connector, ETL-job, portal za stranke) pravilno reagira. Nekaj praktičnih vodil, ki so se obnesla v Delphi/RemObjects-okoljih:

  • 400 Bad Request: Validacija, manjkajoči/neveljavni parametri, JSON ni parsabilen. Pomembno: odgovor naj ostane stabilen, tudi če je body poškodovan.
  • 401/403: Ločite avtentikacijo in avtorizacijo. 401 pomeni „brez ali neveljavna identiteta“, 403 pa „identiteta je v redu, vendar je dostop prepovedan“.
  • 404: Vir ne obstaja. Previdnost z varnostjo: ne razkrivajte vedno, ali nekaj obstaja.
  • 409 Conflict: Strokovni/poslovni konflikt (npr. konflikt verzij, „status ne dovoljuje te akcije“, kršitev unikatnega ključa, če ima to poslovni pomen).
  • 422 Unprocessable Content: Če je sintaktično vse v redu, a ne uspe poslovna validacija (ne uporablja vsaka ekipa 422, vendar je pogosto jasneje kot 400).
  • 500: Vse, kar ne morete dosledno klasificirati. Sem sodijo tudi „DB down“, „Timeout“, „Unhandled Exception“.

Delphi-specifičen trik: Veliko DB-napak se dvigne kot generične izjeme. Izplača se na nivoju dostopa 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 response.

Debugging-trik: reproducibilne napake z »Contract Snapshot«

Neobičajno, a v obratovanju izjemno koristno: ob napakah (ali ciljano za določene Correlation-IDs) shranite „snapshot“ iz Request-Headerjev + Request-Body v datoteko Debug-Spool. To ni stalno logiranje (varstvo podatkov/obseg), temveč kontrolirano orodje za ponovitev težko reproducibilnih primerov blizu produkcije.

Pomembno: Snapshot nikoli ne sme nefiltrirano persistirati Auth-Headerjev, tokenov ali osebnih podatkov. V praksi to pomeni: redaction (maskiranje) in aktivacija le preko feature-flag ali bele liste (npr. samo za določene Correlation-IDs, za kratka časovna okna).

Čista izvedba v praksi: maskiranje namesto izpuščanja

V resničnih integracijah so ravno „kritična“ polja pogosto tista, ki jih potrebujete za debug (npr. identifikatorji). Namesto splošnega izpuščanja je bolje maskirati: delno zamenjati tokene, pri e-pošti obdržati le domeno, pri IBAN-u le zadnje števke. Tako ostane primer reproducibilen, brez raztrosanja nepotrebnih podatkov po datotečnem sistemu. Poleg tega naj bo snapshot jasno označen kot debug-artifakt in naj ima določeno časovno obdobje hrambe.

Varnost in obratovanje: posredovanje headerjev, proxy-verige in časovne omejitve

API REST redko konča neposredno pri odjemalcu. Tipično so vmes verige Reverse Proxy, TLS-terminacija, WAF ali API-Gateway. Iz tega izhajajo praktične točke:

  • Oddaljeni IP: Ne zanašajte se slepo na X-Forwarded-For. Sprejemajte ga le od zaupanja vrednih proxyjev, sicer uporabite neposredno socket-IP. V operativnih priročnikih naj bo navedeno, kateri hopi so »zaupanja vredni«.
  • Časovne omejitve: Če ima proxy 30 sekund, vaše backend pa potrebuje 2 minuti, boste ustvarili ghost-requeste. Določite časovne omejitve konsistentno vzdolž verige in se odločite: sinhroni zahtevek ali vzorec Job (202 Accepted + statusni endpoint).
  • Correlation-ID: Nastavite Correlation-ID v response-headerjih, da jo lahko skrbniki združijo iz logov in strani odjemalca. Če gateway uporablja lastne Request-IDs: logirajte in preslikajte obe ID.
  • Besedila napak: V produkcijskem okolju brez notranjih podrobnosti. Podrobnosti za debugging le nadzorovano (Stage/Feature-Flag) in v primeru dvoma le v logu.

Uvrstitev: Zakaj je RemObjects SDK tukaj lahko v prednosti

V Delphi-ekosistemih se REST-Server pogosto gradijo z lažjimi ogrodji (npr. minimalistični HTTP-Router). RemObjects SDK pokaže svojo moč, ko že imate ali potrebujete večslojno arhitekturo:

  • Jasne meje storitev: metode storitev so eksplicitne, kontrakti podpirajo verzioniranje.
  • Transporti in serializacija: Lahko govorite JSON, pa tudi druge formate sporočil (odvisno od nastavitve), brez premešavanja poslovne logike.
  • Obratovanje: možnosti hostinga in integracija v obstoječe Windows- in Linux-storitve so načrtljive, vključno z urejenimi uvajanji.

Prikazani pristop dopolni to z elementi, ki pogosto manjkajo v vsakdanjem delu: enotni objekti napak, deterministično verzioniranje in korrelirano beleženje. Pri individualni podjetniški programski opremi z dolgimi življenjskimi cikli vam to prihrani čas pri posodobitvah in pri integraciji zunanjih sistemov.

Zaključek: Ali se trud obrestuje – in kje pristop zaide?

Večja vrednost nastane, ko vaša REST-vmesnik ne le »deluje«, ampak je trajnostno upravljiv: stabilni JSON-pogodki, verzioniranje brez nenadzorovanega razraščanja URL-jev, sledljive napake in razhroščevanje brez ugibanja. Ravno pri tem je pristop s kontekstom, Correlation-ID in centralnim preslikovanjem izjem v RemObjects SDK močan.

Meje uporabe: Če imate le en sam, kratkotrajen endpoint brez integracijskih partnerjev, se versioniranje po mediatipu hitro zdi kot overengineering. Tudi Snapshot-Logging je smiselno le, če disciplinirano implementirate Redaction in Aktivierung. In: če vaš proxy-stack headerje »optimizira« ali odstranjuje, morate najprej urediti infrastrukturo, drugače boste odpravljali napake v napačnem sloju.

Če modernizirate obstoječo Delphi-strežniško krajino ali morate procesno bližnjo programsko rešitev čisto integrirati v ERP/DMS/CRM, so prav ti mehanizmi pogosto razlika med »deluje v testu« in »deluje v obratovanju«.

V strokovnem okolju imajo pomembno vlogo tudi Delphi REST-API in REST-Server in Remobjects Sdk Delphi, kadar morajo integracije, podatkovni tokovi in nadaljnji razvoj delovati usklajeno.

Projekt ali modernizacijsko pobudo obravnajte z Net-Base.

naslednji korak

Ko iz teme nastane resničen projekt, je treba arhitekturo, obstoječe sisteme 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 Rollout ne bodo prestavljeni v kasnejše faze.
  • Že zgodaj vidite, katera pot je ekonomsko in operativno vzdržna.

Deli objavo

Deli ta prispevek neposredno

LinkedIn, X, XING, Facebook, WhatsApp in e-pošta so takoj na voljo. Za Instagram pripravljamo povezavo in kratek tekst.

E-pošta

Instagram se odpre v novem zavihku. Povezava in kratek opis se pred tem kopirata v odložišče.