Net-Base Žurnāls

09.06.2026

REST API ar RemObjects SDK: JSON galapunktu skaidra versiju pārvaldība un atkļūdošana (Delphi koda fragments)

Kā ar RemObjects SDK Delphi izveidot REST API, kas darbībā neizjūk: stabilas JSON līgumu definīcijas, versiju pārvaldība bez URL haosa, Correlation-ID caur visiem slāņiem, centrāla kļūdu kartēšana, Snapshot-Logging sarežģītiem atkļūdošanas gadījumiem, kā arī praktiski norādījumi...

09.06.2026

No žurnāla tēmas līdz projektu praksei

Atbilstošas pakalpojumu un tehniskās lapas rakstam

Kāpēc „REST API ar RemObjects SDK” praksē bieži nosaka iznākumu tieši sistēmas robežās

Viens REST API ar RemObjects SDK reti tiek novērtēts pēc “Hello World” servisa; izšķirošās vietas parasti ir tur, kur saskaras ekspluatācija, mantojuma sistēmas un integrācija: versiju pārvaldība bez apstāšanās, konsekventa kļūdu uzvedība visos galapunktos, reproducējama atkļūdošana caur proksi ķēdēm un spēja problēmu gadījumā viennozīmīgi korelēt pieprasījumus.

RemObjects SDK nodrošina daudz infrastruktūras: servisi, ziņojumu formāti, serializācija, hostings (piem., kā Windows- un Linux-servisi vai aiz IIS/Reverse Proxy) un definētas vietas, lai centrāli apstrādātu kļūdas. Taču izaugētās biznesa programmatūras ainavās bieži trūkst konsekventi ieviesta līguma: kuri JSON lauki ir stabilie? Kā mēs signalizējam kļūdas? Kā atpazīt pieprasījumu, ja tas ir gājis cauri slodzes balansētājam, TLS-terminācijai un vairākām aizmugures slāņu kārtām?

Šāds piegājiens (ieskaitot Delphi koda fragmentus) parāda robustu līniju darbam ar RemObjects SDK: JSON‑līgumu versiju vadība, Correlation-ID (Request‑ID izsekošanai) piespiest, Exceptions pārvērst par HTTP statusiem un JSON kļūdu objektiem un tajā pašā laikā nedalīt atkļūdošanu un ekspluatāciju. Papildus aplūkojam arī malas gadījumus, kas reālās vidēs parādās regulāri: servera threadings, datubāzes piekļuves saistībā ar BDE-aizvietošanu ar natīvu pieslēgumu, proxy headeri, timeoutu scenāriji un “netīras” klienta payloads.

Arhitektūras lēmums: versiju noteikšana caur mediju tipu, nevis URL

Daudzas API versijas tiek norādītas ceļā, piemēram ar /v1/. Tas ir pragmatiski, taču ilgstošās integrācijās (piem., ERP/DMS/CRM savienojumi) tas bieži noved pie URL dublēšanās, dubultām maršrutēšanām, dubultiem testiem un jautājuma «kuru versiju mēs patiesībā izmantojam?» ekspluatācijas rokasgrāmatās.

Alternatīva ir versiju noteikšana caur Media Type (Content Negotiation). Klients nosūta, piemēram, Accept: application/vnd.company.order+json;v=2. Servers deterministiski nolasa versiju un pielāgo Contract/DTO uzvedību. Tas darbosies arī proksi un kešatmiņu ķēdēs, ja galvenes tiek pārsūtītas korekti. Administratoriem tas ir arī labi pārbaudāms: pieprasījumu var reproducēt ar Curl/Postman bez URL atšķirībām.

RemObjects SDK nav „REST-puristisks”, bet pragmatisks servisa ietvars. Tieši tāpēc mediju tipa variants bieži ir jēgpilns: jūs varat saglabāt stabilus galapunktus un vienlaikus attīstīt līgumus. Svarīgi ir tas, ka versiju vienmēr nolasa, centrāli pieņem lēmumu vienā vietā un šo rezultātu iekļauj jūsu servisa kontekstā.

Kad Accept-Header pieeja var izgāzties?

Praksē ir trīs tipiskas kritiskās vietas, kuras būtu jāadresē laikus:

  • Proxy-Policies: Daži Reverse Proxies/WAF noteikumi normalizē vai filtrē Accept‑header. Tad jūsu API klusi atgriežas pie noklusējuma. Risinājums: eksplicīti pārbaudīt proxy noteikumus, nepieciešamības gadījumā izmantot X-Api-Version kā alternatīvu.
  • Client-Libraries: Dažas HTTP klientu bibliotēkas iestata savus Accept‑header un pārraksta jūsu vērtības. Risinājums: atbalstīt kontrakta versiju arī kā izvēles query parametru (tikai kā rezerves variants), vai servera pusē toleranti parsēt Accept‑header.
  • Kešēšana: Ja tiek izmantota atbilžu kešēšana, kešam jāmainās atkarībā no Accept (Vary: Accept), pretējā gadījumā tas piegādās 1. versiju 2. versijas klientiem. Risinājums: apzināti iestatīt Vary vai izslēgt kešēšanu API līmenī.

Avota fragments: pieprasījuma konteksts, Correlation-ID, versija un kļūdu kartēšana

Kods ir apzināti strukturēts tā, lai to varētu integrēt esošos RemObjects serveru projektos: neliels konteksta slānis, analizators API versijai (no Accept), Correlation-ID mehānisms un centrāla izņēmumu kartēšana. Jēdzieni:

  • Correlation-ID: Unikāls ID katram pieprasījumam, kas tiek iekļauts atbildē un uz kuru atsaucas žurnāli.
  • Exception-Mapping: Iekšējo Delphi izņēmumu tulkošana uz stabilām, klienta apstrādājamām kļūdu struktūrām (ieskaitot HTTP statusu).
  • Contract-Version: JSON līguma versija, kas kontrolē uzvedību un laukus.
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.

Mērķis: stabils pieprasījuma konteksts, nevis „kaut kur Threadlocal“

Šis fragments apzināti nodala: TApiContext ir minimālais stāvoklis, ko vēlaties nodot. In RemObjects SDK daudz kas tiek vadīts caur servera-/channel-kontektu. Heterogēnos projektos (piem., papildus worker-threads, DB-queue, fona uzdevumi) eksplicīta nodošana bieži ir robustāka par implicitajiem Threadlocals, jo tā padara paralelitāti un konteksta maiņu redzamāku.

Rāmnosacījumi: Accept-Header-varianta izmantošana pieļauj, ka jūsu reverse proxy (nginx, IIS ARR, Traefik) pārsūta headeru nemainītu. Dažās vidēs „neparastus“ Accept-Header filtrē vai apvieno.

Riska punkti: Versiju vadība caur Accept ir tikai tik laba, cik jūsu testi. Ja klienti izmanto bibliotēkas, kas pārraksta Accept, API var pēkšņi atgriezties pie noklusējuma. Legacy- klientiem noklusējuma fallback ir jēdzīgs, bet tam jābūt redzamam monitoringā (piem., žurna brīdinājums „Version defaulted“).

Varianti: Ja dodat priekšroku versiju vadībai caur X-Api-Version: parsers ir identisks, mainās tikai avots — cits header. No gateway skatpunkta tas dažkārt ir vieglāk kontrolējams.

Integrācija in RemObjects SDK: Correlation-ID und Exception-Mapping am Service-Einstieg

Patiesā iedarbība rodas, ja mehāniku konsekventi pielieto servera perifērijā: pieprasījuma ieejā vienreiz nolasīt no headeriem, izņēmuma izvadā vienreiz pārtulkot uz stabilu atbildi. Atkarībā no hostinga (piem., RO-HTTP-Server, IIS-Hosting, pašpārvaldīts Windows-/Windows- und Linux-Services) konkrētie hook-punkti atšķiras; princips paliek nemainīgs: konteksta būve, biznesa loģikas izsaukšana, izņēmumu centrāla kartēšana.

RemObjects projektos bieži strādā tieši pie katras servisa metodes. Sākumā tas labi mērogojas, bet ekspluatācijā tas sabrūk: katra metode uzbūvē logēšanu un kļūdu apstrādi atšķirīgi. Tīrs griezums ir Service-Basis vai Dispatcher, kas standardizē.

Praktiska darba plūsma (apzināti īsi un implementācijai tuva)

  1. Nolasīt Correlation-ID no pieprasījuma headera X-Correlation-ID; ja trūkst, ģenerēt servera pusē (piem., GUID).
  2. Nolasīt kontrakta versiju no Accept (vai no X-Api-Version).
  3. Reģistrēt pieprasījuma sākumu: metode, ceļš, Correlation-ID, Remote IP, uzsākt ilguma mērījumu.
  4. Izpildīt biznesa loģiku; DB-piekļuves pēc iespējas kapsulēt transakcijās.
  5. Noķert izņēmumu: noteikt HTTP statusu, ģenerēt JSON kļūdas objektu, iestatīt Response-Header X-Correlation-ID.
  6. Reģistrēt pieprasījuma beigas: statuss, ilgums, ja nepieciešams kļūdas kods.

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

Bieži sastopams Delphi-gadījums: servisa metode aktivizē asinhronu darbu (piem., atskaišu ģenerēšana, imports, push uz DMS). Tajā brīdī sākotnējais Request-Thread vairs nav tas, kas vēlāk ieraksta logus. Ja Correlation-ID ir zināma tikai „sākumā“, izsekojamība sabrūk.

Pragmatisks noteikums: viss, kas nepaliek striktā Request-Thread, saņem kontekstu eksplicīti nodotu. Pat ja tas izskatās pēc garākiem parametru sarakstiem, tas atmaksājas. Alternatīvi var strādāt ar skaidri definētu konteksta objektu, kas apzināti tiek nodots workeriem (nevis globālām mainīgajām vai slēptiem singletoniem).

Tipiskie kritiskie punkti RemObjects-/Delphi-serveros:

  • DB-Connections uz vītni: BDE-Ablosung mit nativer Anbindung-savienojumi netiek automātiski droši dalīti starp pavedieniem. Savienojumu pūls (Connection-Pool) vai katram pavedienam sava savienojuma izmantošana bieži ir saprātīgāka par „vienu globālu Connection“.
  • Transakciju robežas: Ja pieprasījumā ir vairāki ar vienu loģisku darbību saistīti soļi, transakcijai jāpaliek tajā pašā loģiskajā vienībā. Asinhrons darbs nedrīkst „nejauši“ turpināties tajā pašā transakcijā.
  • Atcelšana: Ja klients pārtrauc (proxy timeout, pārlūkprogrammas aizvēršana), serveris bieži turpina darbu. Apsveriet apzināti, vai fonā veicamajai darbībai tajā gadījumā vēl jēga.

Datu piekļuve un kļūdu kodi: 409 nav „tāpat kā 500“

Integrācijas projektos rūpīga Error-Mapping nav tikai kosmētika. Tas nosaka, vai oponents (ERP-Connector, ETL-uzdevums, klientu portāls) var reaģēt korekti. Dažas praktiskas vadlīnijas, kas ir sevi attaisnojušas Delphi/RemObjects vidēs:

  • 400 Bad Request: Validācija, trūkstoši vai nederīgi parametri, JSON neparsējams. Svarīgi: atbildei jāpaliek stabilai, pat ja pieprasījuma ķermenis ir bojāts.
  • 401/403: Atšķiriet autentifikāciju no autorizācijas. 401 nozīmē „nav/ir nederīga identitāte“, 403 — „identitāte kārtībā, bet darbība aizliegta“.
  • 404: Resurss neeksistē. Drošības apsvērumu dēļ piesardzība: ne vienmēr jāatklāj, vai kaut kas pastāv.
  • 409 Conflict: Biznesa konflikts (piem., versiju konflikts, „statuss neļauj šo darbību“, unikālas atslēgas pārkāpums, ja tas ir nozīmīgi no biznesa viedokļa).
  • 422 Unprocessable Content: Ja sintakse ir kārtībā, bet biznesa validācija neizdodas (ne visas komandas izmanto 422, taču tas bieži ir skaidrāks nekā 400).
  • 500: Viss, ko nevarat skaidri klasificēt. Tajā ietilpst arī „DB down“, „Timeout“, „Unhandled Exception“.

Delphi-specifisks paņēmiens: daudzas DB kļūdas parādās kā ģeneriskas izņēmuma situācijas. Ir vērts datu piekļuves slānī mērķtiecīgi pārbaudīt zināmas situācijas un pārvērst tās EApiError. Svarīgi: neiekļaujiet klienta ziņojumā SQL fragmentus vai iekšējos tabulu/kolonnu nosaukumus. Šīs detaļas pieder logiem, ne atbildei.

Atkļūdošanas paņēmiens: reproducējamas kļūdas ar „Contract Snapshot“

Neparasti, bet ekspluatācijā ārkārtīgi noderīgi: saglabājiet kļūdu gadījumos (vai mērķtiecīgi pie noteiktām Correlation-IDs) „snapshotu“ no pieprasījuma galvenēm + pieprasījuma ķermeņa debug-spool failā. Tas nav pastāvīgs žurnāls (datu aizsardzība/tilpums), bet kontrolēts instruments, lai no ražošanas vides atdarinātu grūti reproducējamus gadījumus.

Svarīgi: snapshot nedrīkst nekad bez filtrēšanas saglabāt Auth-galvenes, tokenus vai personas datus. Praktiski tas nozīmē: redaction (maskierung) un aktivēšana tikai caur feature-flag vai whitelist (piem., tikai noteiktām Correlation-IDs, īsiem laika logiem).

Tīra īstenošana praksē: maskēšana, nevis izslēgšana

Reālās integrācijās tieši „kritiskie“ lauki bieži vien ir tie, kas nepieciešami atkļūdošanai (piem., identifikatori). Vietā vispārējas izslēgšanas labāk izmantot maskēšanu: daļēji aizstāt tokenus, e-pastā saglabāt tikai domēnu, IBAN — tikai pēdējos ciparus. Tā incidents paliek reproducējams, neradot nevajadzīgu datu uzkrāšanu failu sistēmā. Papildus snapshot jāmarķē skaidri kā debug artefakts un tam jāpiešķir definēts glabāšanas laiks.

Drošība un darbība: galvenes nodošana, starpniekserveru ķēdes un timeouti

Vienas REST API reti beidzas tieši pie klienta. Bieži sastopamas ķēdes: Reverse Proxy, TLS-Termination, WAF vai API‑Gateway. No tā izriet praktiski punkti:

  • Remote IP: Neuzticieties akli X-Forwarded-For. Pāņemiet to tikai no uzticamiem proxy, citādi izmantojiet tiešo socket IP. Ekspluatācijas rokasgrāmatās jānorāda, kuri hopi ir „trusted“.
  • Timeouts: Ja proxy pieļauj 30 sekundes, bet jūsu backendam nepieciešamas 2 minūtes, rodas ghost‑requesti. Nosakiet timeoutus konsekventi gar visu ķēdi un izlemiet: sinhrons pieprasījums vai job‑pattern (202 Accepted + statusa galapunkts).
  • Correlation-ID: Ievietojiet Correlation‑ID atbildes galvenēs, lai administratoriem būtu iespējams to sasaistīt no logiem un klienta puses. Ja gateway izmanto savas Request‑ID: reģistrējiet un sasaistiet abas ID.
  • Kļūdu teksti: Produktīvajā darbībā bez iekšējām detaļām. Debug‑detalizāciju rādīt tikai kontrolēti (Stage/Feature‑Flag) un, ja nepieciešams, tikai logā.

Skaidrojums: Kāpēc RemObjects SDK šeit var būt priekšrocība

Delphi-ekosistēmās REST-serverus bieži veido ar vieglākiem ietvariem (piem., minimālistiskiem HTTP‑routeriem). RemObjects SDK izpauž savu spēku, ja jums jau ir vai nepieciešama daudzslāņu arhitektūra:

  • Skaidras servisa robežas: servisa metodes ir skaidri definētas, kontrakti ir versijojami.
  • Transporti un serializācija: Varat izmantot JSON, kā arī citus ziņojumu formātus (atkarībā no konfigurācijas), nesajaucot biznesa loģiku.
  • Darbība: hostinga opcijas un integrācija esošajos Windows- und Linux-Services ir plānojama, ieskaitot sakārtotu izvietošanu.

Parādītais piegājiens papildina to ar daļām, kas ikdienā bieži trūkst: vienoti kļūdu objekti, deterministiska versiju pārvaldība un korelējama žurnēšana. Sevišķi individuālajā uzņēmumu programmatūrā ar ilgām dzīves cikliem tas ietaupa laiku atjauninājumos un ārējo sistēmu integrācijā.

Secinājums: Vai piepūle atmaksājas — un kur pieeja vairs nav piemērota?

Vērtība rodas, ja jūsu REST-saskarne ne tikai „darbojas“, bet ilgtermiņā ir uzturama: stabilas JSON‑līguma definīcijas, versiju vadība bez URL haosa, saprotamas kļūdas un debugging bez minēšanas. Tieši šajā jomā pieeja ar Context, Correlation‑ID un centralizētu Exception‑Mapping RemObjects SDK ir spēcīga.

Izmantošanas robežas: Ja jums ir tikai viens, īslaicīgs endpunkts bez integrācijas partneriem, Media‑Type‑Versionierung ātri var izskatīties pēc overengineering. Arī Snapshot‑Logging ir jēdzīgs tikai tad, ja Redaction un aktivācija tiek disciplinēti ieviestas. Un: ja jūsu proxy‑slānis galvenes „optimizē“ vai noņem, vispirms jānostiprina infrastruktūra, citādi debugosiet nepareizo slāni.

Ja modernizējat esošu Delphi-serveru ainavu vai tīri integrējat procesam tuvu programmatūras risinājumu ERP/DMS/CRM, tieši šīs mehānikas bieži nosaka atšķirību starp „darbojas testos“ un „darbojas ražošanā“.

Funkcionālajā kontekstā nozīmīgu lomu arī spēlē Delphi REST-API un REST-Server un Remobjects Sdk Delphi, ja integrācijām, datu plūsmām un turpmākajai attīstībai jādarbojas saskaņoti.

Apspriest projektu vai modernizācijas ieceri ar Net-Base.

Nākamais solis

Ja no tēmas rodas reāls projekts, arhitektūra, esošais stāvoklis un ekspluatācija būtu jāizskata kopā jau agri.

Mēs atbalstām ne tikai atsevišķu jautājumu risināšanā, bet arī tad, kad no avota koda fragmentiem, mantojuma sistēmu jautājumiem vai portāla idejām jāizveido stabils uzņēmuma līmeņa projekts.

  • Esošais stāvoklis, mērķa stāvoklis un tehniskie riski tiek kopīgi vērtēti.
  • REST, datu piekļuve, portāli un izvēršana netiek atlikti kā vēlākas sekas.
  • Jūs savlaicīgi redzat, kurš ceļš ir ekonomiski un darbības ziņā dzīvotspējīgs.

Kopīgot ierakstu

Kopīgot šo ierakstu tieši

LinkedIn, X, XING, Facebook, WhatsApp un e-pasts ir uzreiz pieejami. Instagramam saiti un īsu tekstu sagatavosim nekavējoties.

E-pasts

Instagram atveras jaunā cilnē. Saite un īss teksts tiek iepriekš nokopēti starpliktuvē.