Net-Base Časopis

09.06.2026

REST API sa RemObjects SDK: JSON krajnje tačke jasno verzionisati i debugovati (Delphi izvorni isječak)

Kako pomoću RemObjects SDK u Delphi izgraditi REST API koji u radu ostaje stabilan: stabilni JSON ugovori, verzioniranje bez neurednog množenja URL-ova, Correlation-ID kroz sve slojeve, centralno mapiranje grešaka, snapshot-logiranje za zahtjevne debug-slučajeve te praktični savjeti...

09.06.2026

Od teme magazina do projektne prakse

Povezane stranice usluga i tehnologije za članak

Zašto „REST API sa RemObjects SDK“ u praksi često odlučuje na rubovima

Jedan REST API sa RemObjects SDK rijetko se dokazuje na „Hello World“ servisu, već na mjestima gdje se operacija, legacy i integracija sudaraju: verzioniranje bez zastoja, konzistentno ponašanje pri greškama na svim endpointima, reproducibilno otklanjanje grešaka kroz proxy lance i sposobnost jednoznačnog korigovanja zahtjeva u slučaju problema.

RemObjects SDK pruža mnogo infrastrukture: servise, formate poruka, serijalizaciju, hosting (npr. kao Windows- und Linux-servisi ili iza IIS/Reverse Proxy) i definirane točke za centralno rukovanje greškama. Ono što u etabliranim poslovnim softverskim okruženjima često nedostaje jest dosljedan ugovor: Koja JSON-polja su stabilna? Kako signaliziramo greške? Kako ponovo identificirati zahtjev nakon što je prošao kroz load balancer, TLS-terminaciju i više backend slojeva?

Sljedeći pristup (uključujući Delphi-isječak) pokazuje robusnu liniju za RemObjects SDK: verzioniranje JSON-ugovora, nametanje Correlation-ID (Request-ID za praćenje), prevođenje iznimki u HTTP status i JSON-objekte grešaka i pritom ne stavljati otklanjanje grešaka i operaciju u konflikt. Dodatno razmatramo rubne slučajeve koji se u stvarnim okruženjima redovno javljaju: upravljanje nitima na serveru, pristupi bazi podataka pri BDE-zamjeni s nativnom vezom, proxy headeri, timeouti i „prljavi“ payloadi klijenta.

Arhitektonska odluka: Verzije preko medijskog tipa umjesto URL-a

Mnoge API-je verzioniraju putem putanja kao što je /v1/. To je pragmatično, ali u dugotrajnim integracijama (npr. priključci ERP/DMS/CRM) često vodi dupliciranju URL-ova, duplim rutama, duplim testovima i pitanju „Koju verziju zapravo koristimo?“ u operativnoj dokumentaciji.

Alternativa je verzioniranje preko medijskog tipa (Content Negotiation). Klijent npr. pošalje Accept: application/vnd.company.order+json;v=2. Server deterministički očitava verziju i prilagođava ponašanje ugovora/DTO-a. To radi kroz proxy i cache lance, pod uvjetom da se headeri pravilno prosljeđuju. Za administratore je dodatno pregledno: zahtjev se može reproducirati putem Curl/Postman bez razlika u URL-ovima.

RemObjects SDK nije „REST-purističan“, već pragmatičan service-framework. Upravo zbog toga varijanta s medijskim tipom ima smisla: možete zadržati stabilne endpoint-e i istovremeno razvijati ugovore. Bitno je da verziju uvijek evaluirate, centralno odlučite i rezultat prenesete u kontekst servisa.

Kada varijanta s Accept headerom klizne?

U praksi postoje tri tipične slabe točke koje treba unaprijed adresirati:

  • Proxy-politike: Neki reverse proxyji ili WAF pravila normaliziraju ili filtriraju Accept header. U tom slučaju vaša API tiho pada na default. Rješenje: eksplicitno provjerite proxy pravila, po potrebi se oslonite na X-Api-Version kao alternativu.
  • Klijentske biblioteke: Neki HTTP-klijenti postavljaju vlastite Accept headere i prebrisavaju vrijednosti. Rješenje: podržite verziju ugovora i kao opcionalni query-parametar (samo kao fallback), ili parsirajte Accept header na serveru tolerantnije.
  • Caching: Ako je aktivno Response-Caching, cache mora varirati prema Accept (Vary: Accept), inače će isporučivati verziju 1 klijentima verzije 2. Rješenje: eksplicitno postaviti Vary ili onemogućiti keširanje na razini API-ja.

Izvorni isječak: Request-Context, Correlation-ID, Version i Error-Mapping

Kod je namjerno strukturiran tako da se može integrirati u postojeće RemObjects-Serverprojekte: mali kontekstni sloj, parser za verziju API-ja (iz Accept), mehanizam Correlation-ID i centralno Exception-Mapping. Pojmovi:

  • Correlation-ID: Jedinstveni ID za svaki zahtjev koji se pojavljuje u odgovoru i na koji se referencira u logovima.
  • Exception-Mapping: Prevođenje internih Delphi-Exceptions u stabilne, za klijenta obradive objekte grešaka (uključujući HTTP-Status).
  • Contract-Version: Verzija JSON-kontrakta koja upravlja ponašanjem i poljima.
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.

Svrha: Stabilan kontekst zahtjeva umjesto „irgendwo im Threadlocal“

Snippet namjerno razdvaja: TApiContext predstavlja minimalno stanje koje želite proslijediti. U RemObjects SDK mnogo toga radi preko server-/channel-konteksta. U heterogenim projektima (npr. dodatni worker-threadovi, DB-queue, pozadinski poslovi) eksplicitno prosljeđivanje često je robusnije od implicitnih threadlocal-a, jer time jasno vidite konkurentnost i promjene konteksta.

Preduvjeti: Varijanta s Accept-headerom podrazumijeva da vaš reverse proxy (nginx, IIS ARR, Traefik) header prosljeđuje neizmijenjen. U nekim okruženjima „neobični“ Accept-header-i se filtriraju ili agregiraju.

Zamke: Verzioniranje preko Accept je samo toliko dobro koliko i vaši testovi. Ako klijenti koriste biblioteke koje prepisuju Accept, API može iznenada pasti na default. Za legacy-klijente smislen je default-fallback, ali on mora biti vidljiv u monitoringu (npr. log-upozorenje „Version defaulted“).

Varijante: Ako radije radite verzioniranje preko X-Api-Version: parser je identičan, samo je izvor drugi header. Iz perspektive gateway-a to je ponekad lakše kontrolirati.

Integration in RemObjects SDK: Correlation-ID und Exception-Mapping am Service-Einstieg

Stvarna korist nastaje kad mehaniku konzistentno primijenite na rubu vašeg servera: jednom na ulazu zahtjeva pročitate iz headera, jednom na izlazu iz iznimke prevedete u stabilan response. Ovisno o hostingu (npr. RO-HTTP-Server, IIS-hosting, samostalno vođeni Windows-/Windows- und Linux-Services) konkretne točke za hook razlikuju se; princip ostaje isti: izgraditi Context, pozvati poslovnu logiku, centralno mapirati Exceptions.

U RemObjects-projektima često se radi direktno po metodi servisa. To na početku dobro skalira, ali u produkciji puca: svaka metoda gradi logging i upravljanje greškama drugačije. Čist rez je osnovna servisna klasa ili Dispatcher koja standardizira.

Praktični postupak (namjerno kratak i implementacijski blizu)

  1. Pročitati Correlation-ID iz Request-Headera X-Correlation-ID; ako nedostaje, generisati ga na serverskoj strani (npr. GUID).
  2. Pročitati verziju contracta iz Accept (ili iz X-Api-Version).
  3. Zabilježiti početak zahtjeva u logu: metoda, putanja, Correlation-ID, udaljena IP adresa, pokrenuti mjerenje trajanja.
  4. Izvršiti poslovnu logiku; DB-pristupe po mogućnosti enkapsulirati transakcionalno.
  5. Uhvatiti iznimku: odrediti HTTP status, generisati JSON-objekt greške, postaviti Response-Header X-Correlation-ID.
  6. Zabilježiti kraj zahtjeva u logu: status, trajanje, eventualno kod greške.

Threading im Server: Warum Correlation-ID ohne Kontext-Disziplin wertlos wird

Čest Delphi-rubni slučaj: metoda servisa pokreće asinkrone poslove (npr. generiranje izvještaja, import, push u DMS). Tada izvorni request-thread više nije onaj koji kasnije piše log-redove. Ako je Correlation-ID poznata samo „na početku“, mogućnost praćenja se raspada.

Pragmatično pravilo: Sve što ne ostaje striktno u request-threadu treba dobiti Context eksplicitno proslijeđen. Iako to izgleda kao više parametara, isplati se. Alternativno se može raditi s jasno definisanim objektom konteksta koji se svjesno prosljeđuje worker-ima (umjesto globalnih varijabli ili skrivenih singleton-a).

Tipične tačke lomljenja u RemObjects-/Delphi-serverima:

  • DB-Connections po threadu: BDE-Ablosung mit nativer Anbindung-veze se ne mogu automatski sigurno deliti među threadovima. Ein Connection-Pool oder pro Thread eine Verbindung ist häufig sinnvoller als „eine globale Connection“.
  • Transaktionsgrenzen: Ako unutar jednog requesta imate više koraka koji pripadaju zajedno, transakcija mora ostati u istoj logičkoj jedinici. Asinhroni rad ne smije „slučajno“ nastaviti unutar iste transakcije.
  • Cancellation: Ako klijent prekine (Proxy timeout, Browser closed), server često nastavi raditi. Razmislite svjesno da li pozadinski rad tada još ima smisla.

Pristup podacima i šifre grešaka: 409 nije „takođe 500“

U integracijskim projektima čisto mapiranje grešaka je više od kozmetike. Ono odlučuje da li druga strana (ERP-Connector, ETL-Job, portal za klijente) može pravilno reagovati. Nekoliko praktičnih smjernica koje su se pokazale u Delphi/RemObjects-okolju:

  • 400 Bad Request: Validacija, nedostajući/neispravni parametri, JSON se ne može parsirati. Važno: Odgovor treba ostati stabilan, čak i ako je body oštećen.
  • 401/403: Razdvojite autentifikaciju i autorizaciju. 401 znači „nema/nevažeći identitet“, 403 „identitet u redu, ali zabranjeno“.
  • 404: Resurs ne postoji. Pažnja pri sigurnosti: Ne otkrivajte uvijek da li nešto postoji.
  • 409 Conflict: Poslovni konflikt (npr. konflikt verzija, „status ne dozvoljava ovu akciju“, kršenje jedinstvenog ključa ako je poslovno relevantno).
  • 422 Unprocessable Content: Ako je sintaktički sve u redu, ali poslovna validacija ne uspeva (ne svaki tim koristi 422, ali često je jasnije od 400).
  • 500: Sve što ne možete jasno klasifikovati. Tu spadaju i „DB down“, „Timeout“, „Unhandled Exception“.

Delphi-specifičan trik: Mnoge DB-greške se pojavljuju kao generičke exceptions. Isplati se u sloju pristupa podacima ciljano provjeravati poznate situacije i preslikavati ih u EApiError. Važno pri tome: Ne preuzimati SQL-fragmente ili interne nazive tabela/kolona u poruku klijentu. Ti detalji pripadaju u log, ne u odgovor.

Debugging-trik: reproducibilne greške kroz „Contract Snapshot“

Neobično, ali u radu izuzetno korisno: Sačuvajte kod grešaka (ili ciljano za određene Correlation-IDs) „snapshot“ sastavljen od zaglavlja zahtjeva + tijela zahtjeva u debug-spool datoteku. To nije stalno logiranje (zaštita podataka/volumen), već kontrolisani alat za rekonstrukciju teško reproducibilnih slučajeva blizu produkcije.

Važno: Snapshot nikada ne smije nefiltrirano perzistirati auth-header-e, tokene ili lične podatke. U praksi to znači: Redaction (maskiranje) i aktivacija samo preko feature-flaga ili whitelist-e (npr. samo za određene Correlation-IDs, kratki vremenski prozori).

Čista implementacija u praksi: maskiranje umjesto izostavljanja

U stvarnim integracijama upravo su „kritična“ polja često ona koja su potrebna za debug (npr. identifikatori). Umjesto općeg izostavljanja, bolje je maskiranje: djelimično zamijeniti tokene, e-mail zadržati samo domenu, IBAN ostaviti samo posljednje cifre. Tako ostaje slučaj reproducibilan, bez nepotrebnog širenja podataka po datotečnom sistemu. Dodatno, snapshot bi trebao biti jasno označen kao debug-artefakt i imati definirano vrijeme čuvanja.

Sigurnost i operativni rad: prosljeđivanje zaglavlja, proxy-lanci i 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:

  • Udaljena IP adresa: Ne oslanjajte se slijepo na X-Forwarded-For. Prihvaćajte ga samo od pouzdanih proxyja, a u suprotnom koristite direktnu socket-IP adresu. U priručnicima za operativni rad treba jasno navesti koji su hopovi „trusted“.
  • Timeouti: Ako proxy ima timeout 30 sekundi, a vaše backend zahtijeva 2 minute, stvorit ćete ghost-zahtjeve. Postavite timeout-e konzistentno duž lanca i odlučite: sinhroni zahtjev ili Job-Pattern (202 Accepted + Status-Endpunkt).
  • Correlation-ID: Postavite Correlation-ID u response-zaglavlja, tako da administratori mogu spojiti podatke iz logova i klijentske strane. Ako gateway koristi vlastite Request-IDs: logirajte i mapirajte obje ID-e.
  • Greške i poruke: U produkciji ne izlažite interne detalje. Debug-detalje dozvolite samo kontrolirano (Stage/Feature-Flag) i, u nedoumici, zabilježite ih samo u logu.

Procjena: 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:

  • Jasne granice servisa: Metode servisa su eksplicitne, ugovori (Contracts) se mogu verzionirati.
  • Transporti i serijalizacija: Možete koristiti JSON, ali i druge formate poruka (ovisno o postavkama), bez miješanja poslovne logike.
  • Operacija: Opcije hostinga i integracija u postojeće Windows- und Linux-Services mogu se planirati, uključujući čiste rolloute.

Predloženi pristup dopunjava to dijelovima koji često nedostaju u svakodnevnom radu: jedinstveni objekti grešaka, determinističko verzioniranje i korrelabilno logiranje. Posebno kod individualnog softvera za preduzeća s dugim životnim ciklusima uštedjet ćete vrijeme pri ažuriranjima i integraciji eksternih sistema.

Zaključak: Isplati li se trud — i kada pristup prestaje biti opravdan?

Vrijednost nastaje kada vaša REST-schnittstelle ne samo da „funkcionira“, već je i dugoročno upravljiva: stabilni JSON-ugovori, verzioniranje bez razrasta URL-ova, razumljive greške i debugiranje bez nagađanja. Upravo tu je pristup s Context, Correlation-ID i centralnim Exception-Mappingom u RemObjects SDK snažan.

Granice primjene: Ako imate samo jedan, kratkotrajan endpoint bez integracijskih partnera, Media-Type-Versionierung brzo djeluje kao overengineering. Snapshot-Logging također ima smisla samo ako disciplinirano implementirate Redaction i Aktivierung. I: ako vaš proxy-stack „optimizira“ ili uklanja zaglavlja, prvo morate srediti infrastrukturu, inače ćete debugirati pogrešni sloj.

Ako modernizirate postojeću Delphi-serverokolicu ili trebate čisto integrirati procesno orijentirano softversko rješenje u ERP/DMS/CRM, upravo ti mehanizmi često čine razliku između „läuft im Test“ i „läuft im Betrieb“.

U stručnom okruženju također važnu ulogu imaju Delphi REST-API i REST-Server i Remobjects Sdk Delphi, kada integracije, tokovi podataka i dalji razvoj moraju neometano surađivati.

Razgovarajte o projektu ili modernizacijskom zahvatu sa Net-Base.

Sljedeći korak

Ako se tema pretvori u stvarni projekat, arhitekturu, postojeći sistem i operacije trebalo bi rano zajednički razmotriti.

Pružamo podršku ne samo pri pojedinačnim pitanjima, već i kada iz fragmenata izvornog koda, naslijeđenih sistema ili ideja za portal treba nastati robustan poslovni projekat.

  • Postojeće stanje, ciljno stanje i tehnički rizici procjenjuju se zajedno.
  • REST, pristup podacima, portali i Rollout neće se odgađati za kasnije faze.
  • Pravovremeno prepoznajete koji pristup je ekonomski i operativno održiv.

Podijeli objavu

Ovu objavu direktno proslijediti

LinkedIn, X, XING, Facebook, WhatsApp i e-pošta su odmah dostupni. Za Instagram pripremamo link i kratak tekst.

E-pošta

Instagram se otvara u novom tabu. Link i kratak tekst se prethodno kopiraju u međuspremnik.