Net-Base Žurnalas

09.06.2026

REST API su RemObjects SDK: JSON galinius taškus nuosekliai versionuoti ir debuginti (Delphi šaltinio kodo fragmentas)

Kaip su RemObjects SDK in Delphi sukurti REST API, kuri eksploatacijoje nesugriūtų: stabilūs JSON kontraktai, versijavimas be URL'ų dauginimosi, Correlation-ID per visus sluoksnius, centralizuotas Error-Mapping, Snapshot-Logging sudėtingiems derinimo atvejams bei praktiški patarimai...

09.06.2026

Nuo žurnalo temos iki projekto įgyvendinimo

Tinkami puslapiai apie paslaugas ir techninę informaciją šiam įrašui

Kodėl „REST API mit RemObjects SDK“ praktikoje dažnai sprendžia ribinėse situacijose

Viena REST API mit RemObjects SDK retai „užsideda“ ar „sugenda“ dėl „Hello World“ serviso — sprendimai priimami ten, kur susikerta eksploatavimas, legacy ir integracija: versijavimas be sustojimo, nuoseklus klaidų elgesys visuose endpoint’uose, reprodukuojamas debug’inimas proxijų grandinėse ir gebėjimas problemai esant vienareikšmiškai koreliuoti užklausas.

RemObjects SDK pateikia daug reikalingos infrastruktūros: servisus, žinučių formatus, serializaciją, hostingą (pvz. kaip Windows- und Linux-Services arba už IIS/Reverse Proxy) ir apibrėžtas vietas, kur centralizuotai tvarkyti klaidas. Tačiau brandžiame verslo programinės įrangos peizaže dažnai trūksta konsekventiškai įgyvendintos sutarties: kurie JSON laukai yra stabilūs? Kaip signalizuojame klaidas? Kaip atpažįstame užklausą, kai ji praeina per Load Balancer, TLS terminaciją ir kelis backend sluoksnius?

Šis požiūris (įskaitant Delphi-Snipsel) pateikia tvirtą gairę RemObjects SDK: versijuoti JSON sutartis, priversti Correlation-ID (Request-ID sekimui), vertinti Exceptions į HTTP statusą ir JSON klaidų objektus ir tuo pačiu neiškelti debug’o prieš eksploatavimą. Papildomai apžvelgsime ribinius atvejus, kurie realiose aplinkose pasitaiko reguliariai: serverio thread’inimas, duomenų bazės prieigos su BDE-Ablösung su natūralia jungtimi, proxy antraštės, timeout’ai ir „nešvarios“ kliento payload’ai.

Architektūrinis sprendimas: versijavimas per Media Type vietoje URL

Daugelis API versijuoja per kelius, pvz. /v1/. Tai pragmatiška, bet ilgalaikėse integracijose (pvz. ERP/DMS/CRM prijungimuose) dažnai veda prie URL dublikavimo, dubliuotų maršrutų, dubliuotų testų ir prie klausimų „Kurią versiją mes iš tikrųjų naudojame?“ eksploatacijos dokumentacijoje.

Alternatyva — versijuoti per Media Type (turinio derinimas, Content Negotiation). Klientas siunčia pvz. Accept: application/vnd.company.order+json;v=2. Serveris deterministiškai nuskaitytų versiją ir pritaikytų Contract/DTO elgesį. Tai veikia proxijų ir cache grandinėse, jeigu antraštės yra perduodamos nepažeistos. Administratoriams tai taip pat lengvai patikrinama: užklausą galima reprodukuoti per Curl/Postman be skirtingų URL.

RemObjects SDK nėra „REST-puristinis“, o pragmatiškas servisų karkasas. Būtent todėl verta naudoti medijos tipo variantą: galite išlaikyti stabilias endpoint‘ų vietas ir tuo pačiu vystyti sutartis. Svarbu, kad versiją visada vertintumėte, centralizuotai priimtumėte sprendimą ir perimtumėte rezultatą į savo servisų kontekstą.

Kada Accept-antraščių variantas gali nepavykti?

Praktikoje yra trys tipinės lūžio vietos, kurias verta iš anksto adresuoti:

  • Proxy-Policies: Kai kurios Reverse Proxies/WAF taisyklės normalizuoja arba filtruoja Accept-antraštes. Tokiu atveju jūsų API tyliai grįžta prie numatytosios versijos. Sprendimas: aiškiai patikrinti proxy taisykles, esant reikalui pereiti prie X-Api-Version.
  • Client-Libraries: Kai kurios HTTP klientų bibliotekos nustato savo Accept-antraštes ir perrašo reikšmes. Sprendimas: palaikyti kontrakto versiją ir kaip opcioninį užklausos parametrą (tik kaip atsarginį variantą), arba serverio pusėje tolerantiškai parsuoti Accept-antraštę.
  • Kešavimas: Jei naudojamas atsakymų kešavimas, kešas turi skirtis pagal Accept (Vary: Accept), kitaip jis pateiks 1-os versijos duomenis 2-os versijos klientams. Sprendimas: aiškiai nustatyti Vary arba išjungti kešavimą API lygyje.
  • Kodo pavyzdys: Request-Context, Correlation-ID, Version ir Error-Mapping

    Kodas sąmoningai paruoštas taip, kad jį būtų galima integruoti į esamus RemObjects-serverio projektus: nedidelis konteksto sluoksnis, API versijos (iš Accept) parseris, Correlation-ID mechanizmas ir centrinis Exception-Mapping. Terminai:

    • Correlation-ID: Unikalus ID kiekvienai užklausai, kuris pasirodo atsakyme ir yra nurodomas žurnaluose.
    • Exception-Mapping: Vidinių Delphi-Exceptions vertimas į stabilias, kliento apdorojimui tinkamas klaidų struktūras (įskaitant HTTP statusą).
    • Contract-Version: JSON kontrakto versija, kuri valdo elgesį ir 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.

    Tikslas: stabilus užklausos kontekstas vietoj „kur nors Threadlocal“

    Šis fragmentas sąmoningai atskiria: TApiContext yra minimalus būsena, kurią norite perduoti. RemObjects SDK daug kas vyksta per serverio-/kanalo kontekstą. Heterogeniniuose projektuose (pvz., papildomi Worker-Threads, DB-queue, foniniai darbai) eksplicitus konteksto perdavimas dažnai yra patikimesnis už implicitinius Threadlocal sprendimus, nes taip lygiagretumas ir konteksto perjungimai tampa matomesni.

    Ribojimai: Accept antraštės variantas reikalauja, kad jūsų reverse proxy (nginx, IIS ARR, Traefik) antraštę perduotų nepakitusią. Kai kuriose aplinkose „netipinės“ Accept antraštės yra filtruojamos arba sujungiamos.

    Pavojai: Versijavimo per Accept patikimumas priklauso nuo jūsų testavimo. Jei klientai naudoja bibliotekas, kurios perrašo Accept, API gali netikėtai sugrįžti prie numatytosios reikšmės. Senesniems klientams verta turėti numatytąjį fallback — jis turi būti matomas monitoring’e (pvz., įrašo į logą įspėjimas „Version defaulted“).

    Variantai: Jei verčiatės versijavimą per X-Api-Version, parselis yra identiškas — tik šaltinis yra kita antraštė. Iš gateway perspektyvos tai kartais lengviau kontroliuoti.

    Integracija į RemObjects SDK: Correlation-ID ir Exception-Mapping prie serviso įėjimo

    Tikroji nauda atsiranda, kai mechaniką taikote konsekiuentiškai serverio krašte: vieną kartą prie užklausos įėjimo nuskaitote iš antraščių, vieną kartą prie išimčių išėjimo verčiate į stabilų atsakymą. Priklausomai nuo hostingo (pvz., RO-HTTP-Server, IIS-Hosting, savarankiškai vykdomi Windows-/Windows- ir Linux-Services) konkretūs hook taškai skirsis; principas lieka tas pats: sukonstruoti Context, iškviesti verslo logiką, centralizuotai map’inti Exceptions.

    RemObjects projektuose dažnai dirbama tiesiogiai su kiekviena serviso metodu. Iš pradžių tai gerai skalė, bet eksploatacijoje tai subyra: kiekviena metoda statys log’ą ir klaidų apdorojimą skirtingai. Švarus pjūvis yra serviso bazė arba dispatcher, kuris tai standardizuoja.

    Praktinis eiga (sąmoningai trumpai ir įgyvendinimo lygmenyje)

    1. Perskaityti Correlation-ID iš užklausos antraštės X-Correlation-ID; jei trūksta, sugeneruoti serveryje (pvz., GUID).
    2. Perskaityti contract-versiją iš Accept (arba iš X-Api-Version).
    3. Užregistruoti užklausos pradžią: metodas, kelias, Correlation-ID, nuotolinė IP, pradėti trukmės matavimą.
    4. Vykdyti verslo logiką; DB prieigą kapsuliuoti kiek įmanoma tranzakciškai.
    5. Pagauti Exception: nustatyti HTTP statusą, sugeneruoti JSON klaidos objektą, nustatyti Response antraštę X-Correlation-ID.
    6. Užregistruoti užklausos pabaigą: statusas, trukmė, jei reikia — klaidos kodas.

    Threading serveryje: kodėl Correlation-ID be konteksto disciplinos tampa bevertis

    Dažnas Delphi kraštinis atvejis: serviso metodas inicijuoja asinchroninį darbą (pvz., ataskaitos generavimas, importas, push į DMS). Tokiu atveju pradinė užklausos gija nebėra ta, kuri vėliau rašo log įrašus. Jei Correlation-ID žinoma tik „pradžioje“, trasabilumas sugriūna.

    Pragmatiška taisyklė: viskas, kas nelieka striktškai užklausos gijoje, turi gauti Context’ą eksplicitiai perduotą. Net jei tai reikštų ilgesnes parametrų eilutes, tai atsiperka. Alternatyviai galima naudoti aiškiai apibrėžtą konteksto objektą, kuris sąmoningai perduodamas worker’iui (vietoje globalių kintamųjų ar paslėptų singletonų).

    Tipiniai lūžio taškai RemObjects-/Delphi serveriuose:

    • DB-Connections pro Thread: BDE-Ablosung mit nativer Anbindung-Verbindungen nėra automatiškai saugiai dalijamos tarp gijų. Connection-Pool arba po vieną jungtį kiekvienai gijai dažnai yra prasmingesnis sprendimas nei „viena globali Connection“.
    • Tranzakcijų ribos: Jei per vieną request vyksta keli susiję žingsniai, tranzakcija turi likti toje pačioje logiškoje vienetėje. Asinchroninis darbas neturėtų „netyčia“ tęstis toje pačioje tranzakcijoje.
    • Atšaukimas: Jei klientas nutraukia (Proxy timeout, Browser closed), serveris dažnai tęs darbą. Apmąstykite sąmoningai, ar foninė veikla tokiu atveju vis dar turi prasmę.

    Datenzugriff und Fehlercodes: 409 ist nicht „auch ein 500“

    Integracijos projektuose aiškus klaidų žemėlapis yra daugiau nei kosmetika. Nuo jo priklauso, ar priešinga pusė (ERP-Connector, ETL-Job, klientų portalas) gali tinkamai reaguoti. Keletas praktinių gairių, kurios pasiteisino Delphi/RemObjects aplinkose:

    • 400 Bad Request: Validacija, trūkstami/neteisingi parametrai, JSON nesukonkretinamas. Svarbu: atsakymas turi išlikti stabilus net jei body yra sugadintas.
    • 401/403: Atskirkite autentifikaciją nuo autorizacijos. 401 reiškia „nėra/negaliojanti tapatybė“, 403 „tapatybė OK, bet draudžiama“.
    • 404: Išteklius neegzistuoja. Atsargiai dėl saugumo: ne visada verta atskleisti, ar kas nors egzistuoja.
    • 409 Conflict: Verslo logikos konfliktas (pvz. versijų konfliktas, „statusas neleidžia šios operacijos“, unikalios reikšmės pažeidimas, jei tai turi verslo reikšmę).
    • 422 Unprocessable Content: Jei sintaksė yra teisinga, bet verslo validacija nepraeina (ne visi komandos naudoja 422, bet dažnai jis aiškesnis nei 400).
    • 500: Viskas, ko negalite aiškiai klasifikuoti. Tai apima „DB down“, „Timeout“, „Unhandled Exception“.

    Delphi-specifinis patarimas: daug DB klaidų pakyla kaip generinės išimtys. Apsimoka duomenų prieigos sluoksnyje aktyviai tikrinti žinomas situacijas ir konvertuoti jas į EApiError. Svarbu: neperkelkite SQL fragmentų ar vidinių lentelių/stulpelių pavadinimų į klientui skirtą žinutę. Šios detalės turi būti loguose, ne responso dalyje.

    Debugging-Kniff: reproduzierbare Fehler durch „Contract Snapshot“

    Nebūdinga, bet eksploatacijoje itin naudinga: klaidos atveju (arba selektyviai pagal tam tikras Correlation-ID) išsaugokite „snapshot“ iš Request-Headerių + Request-Body į debug-spool failą. Tai nėra nuolatinis loggingas (duomenų apsauga/duomenų tūris), o kontroliuojamas įrankis, skirtas sunkiai atkartojamų atvejų reprodukcijai iš gamybinės aplinkos.

    Svarbu: snapshot niekada neturi nefiltruotai saugoti Auth-Headerių, tokenų ar asmens duomenų. Praktikoje tai reiškia: redaction (maskavimas) ir aktyvavimas tik per feature-flag arba baltąjį sąrašą (pvz. tik tam tikroms Correlation-ID, riboti laiko langai).

    Saubere Umsetzung in der Praxis: Maskieren statt Weglassen

    Tikrose integracijose „kritiniai“ laukai dažnai yra tie, kurie reikalingi debuginimui (pvz. identifikatoriai). Vietoje vienodo pašalinimo geriau atlikti maskavimą: dalinai pakeisti tokenus, el. paštą palikti tik domeną, IBAN — tik paskutines skaitmenis. Taip atvejis lieka reprodukuojamas, be to nereikalingi duomenys neplatinami failų sistemoje. Be to, snapshotai turėtų būti aiškiai pažymėti kaip debug-artefaktai ir turėti apibrėžtą saugojimo trukmę.

    Saugumas ir eksploatavimas: antraščių perdavimas, tarpinės proxy grandinės ir laiko limitai

    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:

    • Nuotolinio kliento IP: Verlassen Sie sich nicht blind auf X-Forwarded-For. Nur aus vertrauenswürdigen Proxies übernehmen und sonst die direkte Socket-IP nutzen. In Betriebshandbüchern sollte stehen, welche Hops „trusted“ sind.
    • Laiko limitai: Wenn Proxy 30 Sekunden hat, Ihr Backend aber 2 Minuten braucht, erzeugen Sie Ghost-Requests. Legen Sie Timeouts entlang der Kette konsistent fest und entscheiden Sie: synchroner Request oder Job-Pattern (202 Accepted + Status-Endpunkt).
    • Koreliacijos ID: Setzen Sie die Correlation-ID in Response-Headern, damit Admins sie aus Logs und Client-Seite zusammenführen können. Wenn ein Gateway eigene Request-IDs nutzt: beide IDs loggen und abbilden.
    • Klaidų tekstai: Im Produktivbetrieb keine internen Details. Debug-Details nur kontrolliert (Stage/Feature-Flag) und im Zweifel nur im Log.

    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:

    • Aiškios paslaugų ribos: 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 Context, Correlation-ID und zentralem Exception-Mapping in RemObjects SDK stark.

    Einsatzgrenzen: Wenn Sie nur einen einzelnen, kurzlebigen Endpunkt ohne Integrationspartner haben, wirkt Media-Type-Versionierung schnell wie Overengineering. Auch Snapshot-Logging ist nur sinnvoll, wenn Sie Redaction und Aktivierung diszipliniert implementieren. Und: Wenn Ihr Proxy-Stack Header „optimiert“ oder entfernt, müssen Sie zuerst die Infrastruktur geradeziehen, sonst debuggen Sie die falsche Schicht.

    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“.

    Domeninėje aplinkoje taip pat svarbų vaidmenį atlieka Delphi REST-API ir REST-Server ir Remobjects Sdk Delphi, kai integracijos, duomenų srautai ir tolimesnis vystymas turi sklandžiai sąveikauti.

    Aptarkite projektą arba modernizavimo sumanymą su Net-Base.

    Sekantis žingsnis

    Kai iš temos tampa realus projektas, architektūrą, esamą aplinką ir eksploatavimą reikėtų anksti nagrinėti kartu.

    Mes padedame ne tik pavienėse užklausose, bet ir tuomet, kai iš šaltinio kodo fragmentų, paveldėtų temų ar portalo idėjų turi tapti patikimas įmonės projektas.

    • Esama padėtis, tikslinis vaizdas ir techninės rizikos vertinami kartu.
    • REST, duomenų prieiga, portalai ir diegimas nebus atidedami į vėlesnes stadijas.
    • Jūs anksti matote, kuris kelias yra ekonomiškai ir įmonės veiklos požiūriu tvarus.

    Pasidalinti įrašu

    Tiesiogiai pasidalinti šiuo įrašu

    LinkedIn, X, XING, Facebook, WhatsApp ir el. paštas yra iš karto prieinami. Instagramui parengiame nuorodą ir trumpą tekstą nedelsiant.

    El. paštas

    Instagram atidaromas naujame skirtuke. Nuoroda ir trumpas tekstas iš anksto nukopijuojami į iškarpinę.