Net-Base Ajakiri

09.06.2026

REST API RemObjects SDK-ga: JSON-lõpp-punktide korrektne versioonihaldus ja silumine (Delphi allikakoodilõigud)

Kuidas RemObjects SDK abil Delphi-s ehitada REST API, mis tööajal ei purune: stabiilsed JSON-kokkulepped, versioonihaldus ilma URL-ide kontrollimatu paljunemiseta, Correlation-ID kõigis kihtides, keskne veakaardistamine, snapshot-logimine raskete debug-juhtumite jaoks ning praktilised soovitused...

09.06.2026

Ajakirjateemast projektipraktikasse

Sobivad teenuse- ja tehnilised lehed postituse jaoks

Miks „REST API RemObjects SDK‑ga“ praktikas sageli äärejuhtudel otsustavaks osutub

Üks REST API RemObjects SDK‑ga ei saa harva otsustavat tõuget „Hello World“‑teenusest, vaid nendes kohtades, kus käitus, pärand⁠süsteemid ja integratsioonid kokku põrkuvad: versioonihaldus katkestusteta, ühtlane veakäitumine kõigi lõpp‑punktide juures, taasesitatav silumine proksiahelates ning võime probleemjuhtumites päringuid ühemõtteliselt korreleerida.

RemObjects SDK pakub selleks palju infrastruktuuri: teenuseid, sõnumivorminguid, serialiseerimist, hostimist (nt kui Windows- ja Linux-teenused või IIS/Reverse Proxy taga) ning määratletud kohti vigade tsentraalseks käsitlemiseks. Mis aga väljaarenenud ärirakenduste maastikel sageli puudub, on järjepidevalt rakendatud leping: millised JSON‑väljad on stabiilsed? Kuidas signa‑liseerime vigu? Kuidas tuvastame päringu, kui see on läbi läbinud Load Balanceri, TLS‑terminatsiooni ja mitu backend‑kihti?

Järgmine lähenemine (sh Delphi-koodilõik) näitab RemObjects SDK jaoks robustset joont: JSON‑lepingute versioonimine, Correlation‑ID (päringu‑ID jälgimiseks) sundimine, erandite tõlkimine HTTP‑staatusesse ja JSON‑veaobjektideks ning silumise ja käituse omavahel mitte vastandamine. Lisaks vaatame äärejuhtumeid, mis reaalsetes keskkondades regulaarselt esinevad: serveri threadimine, andmebaasirakendused koos BDE-asendamisega natiivse ühendusega, proxy‑päised, time‑out’id ja „rikkis“ kliendipayloadid.

Arhitektuuriline otsus: versioonimine meediatüübi kaudu, mitte URL‑i kaudu

Paljud API‑d versioonivad teekonna kaudu, nt /v1/. See on pragmaatiline, kuid pikaajalistes integratsioonides (nt ERP/DMS/CRM‑liidesed) viib see tihti URL‑ide dubleerimise, topelt‑routide, topelttestide ja küsimuseni „millist versiooni me tegelikult kasutame?“ operatsioonijuhendites.

Alternatiiviks on versioonimine läbi Media Type (Content Negotiation). Klient saadab nt Accept: application/vnd.company.order+json;v=2. Server loeb versiooni deterministlikult välja ja kohandab Contract/DTO käitumist. See toimib proksi‑ ja vahemäluahelates, kui päised edastatakse puhtalt. Adminide jaoks on see ka hästi kontrollitav: päringu saab Curl/Postmaniga taastoota, ilma et URL‑id erineksid.

RemObjects SDK ei ole „REST‑puristlik“, vaid pragmaatiline teenuste raamistik. Täpselt sellepärast tasub meediatüübi variant: saate hoida stabiilseid lõpp‑punkte ja samal ajal lepinguid edasi arendada. Oluline on, et te versiooni alati tõlgendate, teete tsentraalse otsuse ühes kohas ja kannate väljundi oma teenuse konteksti.

Millal Accept‑päise variant ebaõnnestub?

Praktikas on kolm tüüpilist murdepunkti, mida tuleks eelnevalt adresseerida:

  • Proxy‑poliitikad: Mõned reverse‑proksid/WAF‑reeglid normaliseerivad või filtreerivad Accept‑päist. Sel juhul langeb teie API vaikimisi versioonile tagasi. Lahendus: kontrollige proksi‑reegleid eksplitsiitselt, vajadusel kasutage tagavarana X-Api-Version.
  • Kliendi‑teegid: Mõned HTTP‑kliendid seadistavad oma Accept‑päised ja kirjutavad olemasolevad väärtused üle. Lahendus: toetage lepinguversiooni ka valikulise päringuparameetrina (ainult fallback’ina) või parseerige Accept‑päist serveripoolselt tolerantsemalt.
  • Caching: Wenn Response-Caching im Spiel ist, muss der Cache nach Accept variieren (Vary: Accept), sonst liefert er Version 1 an Version-2-Clients. Lösung: Vary bewusst setzen, oder Caching auf API-Ebene deaktivieren.
  • Koodinäide: Request-Context, Correlation-ID, versioon ja Exception-Mapping

    Kood on teadlikult nii struktureeritud, et seda saab integreerida olemasolevatesse RemObjects-serveriprojektidesse: väike Context-kiht, parser API-versiooni jaoks (päisest Accept), Correlation-ID-mehhanism ja keskne Exception-Mapping. Mõisted:

    • Correlation-ID: iga päringu unikaalne ID, mis ilmub vastuses ja millele viidatakse logides.
    • Exception-Mapping: sisemiste Delphi-Exceptionside teisendamine stabiilseteks, kliendi poolt töödeldavateks veaobjektideks (sh HTTP-status).
    • Contract-Version: JSON-lepingu versioon, mis juhib käitumist ja välju.
    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.

    Eesmärk: stabiilne päringu kontekst, mitte „kusagil Threadlocalis“

    See snippet eraldab sihilikult: TApiContext on minimaalne olek, mida soovite edasi anda. In RemObjects SDK käib palju läbi serveri-/channel-konteksti. Heterogeensetes projektides (nt täiendavad worker-threadid, DB-queue, tausttööd) on eksplicitne edasikandmine sageli robustsem kui implitsiitsed threadlocal’id, sest see muudab paralleelsuse ja konteksti vahetused nähtavamaks.

    Eeltingimused: Accept-päise variant eeldab, et teie reverse proxy (nginx, IIS ARR, Traefik) edastab päise muutmata. Mõnes keskkonnas filtreeritakse või liidetakse „ebaharilikke“ Accept-päiseid.

    Lõksud: Versioonihaldus üle Accepti on nii hea kui teie testid. Kui kliendid kasutavad teeke, mis kirjutavad Accept-päist üle, võib API ootamatult langeda oletusversioonile. Legacy-kliendi jaoks on vaikimisi fallback mõistlik, kuid see peab olema monitooringus nähtav (nt logihoiatus „Version defaulted“).

    Variandid: Kui eelistate versioonihaldust üle X-Api-Version: parser on identne, erineb ainult päise allikas. Gatewayde seisukohast on seda mõnikord lihtsam kontrollida.

    Integratsioon RemObjects SDK-s: Correlation-ID ja Exception-Mapping teenuse sisenemisel

    Tegelikku mõju tekib siis, kui rakendate mehhanismi järjekindlalt serveri servas: ühel hetkel loete päringu sisenemisel päistest, teisel hetkel teisendate erindi väljundi stabiilseks vastuseks. Sõltuvalt hostimisest (nt RO-HTTP-Server, IIS-Hosting, iseseisvalt hallatavad Windows-/Windows- und Linux-Services) erinevad konkreetsed hook-punktid; põhimõte jääb samaks: kontekst ehitada, äriloogika kutsuda, erindid keskelt kaardistada.

    RemObjects-projektides töötatakse sageli iga service-meetodiga otse. Alguses skaleerub see hästi, kuid käigus kipub see probleemseks muutuma: iga meetod ehitab logimise ja veakäsitluse erinevalt. Puhtaks lahenduseks on Service-Basis või Dispatcher, mis standardiseerib.

    Praktiline käik (teadlikult lühike ja implementeerimislähedane)

    1. Correlation-ID päringu päisest X-Correlation-ID lugeda; kui puudub, serveris genereerida (nt GUID).
    2. Contract-Version Accept päisest lugeda (või X-Api-Version).
    3. Logi päringu algus: meetod, path, Correlation-ID, kliendi IP, alusta kestuse mõõtmist.
    4. Käivitage äriloogika; DB-päringud võimalusel transaktsioonidesse kapseldada.
    5. Püüdke erindid: määrake HTTP-seisundi kood, genereerige JSON-veaobjekt, seadke vastuse päis X-Correlation-ID.
    6. Logi päringu lõpp: staatus, kestus, vajadusel veakood.

    Serveri lõimitus: miks Correlation-ID ilma konteksti-distsipliinita väärtusetuks muutub

    Sageli esinev Delphi-äärejuhtum: teenuse meetod trigerdab asünkroonset tööd (nt aruande genereerimine, import, push DMS-i). Sel juhul ei ole algne päringu lõim enam see, mis hiljem logireale kirjutab. Kui Correlation-ID on teada ainult „alguses“, siis kaob jälgitavus.

    Pragmaatiline reegel: kõik, mis ei jää rangelt päringu lõimesse, peab saama konteksti ekspliciitselt üleantuna. Isegi kui see näib tuua pikemaid parameetrite järjekordi, tasub see ära. Alternatiivina saab kasutada selgelt defineeritud kontekstobjekti, mida teadlikult worker’itele antakse (mitte globaalsete muutujate või peidetud singletonite kaudu).

    Tüüpilised kitsaskohad RemObjects-/Delphi-serverites:

    • DB-Connections pro Thread: BDE-Ablosung mit nativer Anbindung-ühendusi ei saa automaatselt lõimide vahel turvaliselt jagada. Connection-Pool või iga lõime jaoks oma ühendus on sageli mõistlikum kui „ühe globaalne Connection“.
    • Transaktionsgrenzen: Kui requesti sees on mitu omavahel seotud sammu, peab transaktsioon jääma samasse loogilisse üksusesse. Asünkroonne töö ei tohi „eksitusest“ jätkuda samas transaktsioonis.
    • Cancellation: Kui klient katkestab (proxy timeout, browser closed), jätkab server tihti tööd. Mõelge läbi, kas taustatöö sel juhul endiselt mõttekas on.

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

    Integratsiooniprojektides on korrektne vigade kaardistamine rohkem kui kosmeetika. See määrab, kas vastaspool (ERP-Connector, ETL-Job, Kundenportal) suudab õigesti reageerida. Mõned praktilised juhised, mis on osutunud kasulikuks Delphi/RemObjects-keskkondades:

    • 400 Bad Request: valideerimine, puuduvad/vigased parameetrid, JSON ei ole parsitav. Oluline: vastus peab jääma stabiilseks ka siis, kui request-Body on rikutud.
    • 401/403: eraldage autentimine ja autoriseerimine. 401 tähendab „puuduv/vigane identiteet“, 403 „identiteet ok, kuid tegevus keelatud“.
    • 404: ressurss ei eksisteeri. Turvalisuse kaalutlustel ei pruugi alati olla soovitatav avaldada, kas midagi eksisteerib.
    • 409 Conflict: äriloogika konflikt (nt versioonikonflikt, „olek ei luba seda toimingut“, unikaalse võtme rikkumine, kui see on äriliselt oluline).
    • 422 Unprocessable Content: kui süntaktiliselt on kõik korras, kuid äriline valideerimine ebaõnnestub (mitte kõik meeskonnad kasutavad 422, kuid see on sageli selgem kui 400).
    • 500: kõik, mida te ei suuda korrektselt klassifitseerida. Siia kuuluvad ka „DB down“, „Timeout“, „Unhandled Exception“.

    Delphi-spetsiifiline nipp: paljud DB-vead tõusevad üles generiliste exception’itena. Tasub andmejuurdepääsu kihis sihipäraselt kontrollida tuntud olukordi ja teisendada need EApiError-ks. Oluline: ärge kandke kliendisõnumisse SQL-fragmente ega sisemiste tabelite-/veergude nimesid. Need detailid kuuluvad logisse, mitte response’i.

    Debugging-Kniff: reproduzierbare Fehler durch „Contract Snapshot“

    Ebatavaline, kuid tootmises äärmiselt kasulik: salvestage vigade korral (või sihipäraselt teatud Correlation-ID-de puhul) „Snapshot“ koos Request-Headerite + Request-Bodyga debug-spool-faili. See ei ole püsiloggimine (andmekaitse/maht), vaid kontrollitud tööriist, mis võimaldab raskesti reprodutseeritavaid juhtumeid tootmislähedases keskkonnas taastoota.

    Oluline: snapshot ei tohi kunagi filtrita salvestada Auth-Header’e, tokeneid ega isikuandmeid. Praktikas tähendab see andmete varjamist (maskimist) ja aktiveerimist ainult feature-flag’i või valgesse nimekirja alusel (nt ainult teatud Correlation-ID-de jaoks, lühikese ajavahemiku jooksul).

    Saubere Umsetzung in der Praxis: Maskieren statt Weglassen

    Tõelistes integratsioonides on tihti just need „kriitilised“ väljad need, mida debugimiseks vaja läheb (nt identifikaatorid). Üldise eemaldamise asemel on parem maskeerida: tokenid osaliselt asendada, e-postist jätta alles ainult domeen, IBANist salvestada ainult viimased numbrid. Nii jääb juhtum reprodutseeritavaks, ilma et failisüsteemi levitataks ülearuseid andmeid. Lisana peaks snapshot olema selgelt märgistatud kui debug-artefakt ja omama määratletud säilitusaega.

    Turvalisus ja haldus: päiste edastamine, proksi-ketid ja time‑out’id

    Üks REST API lõpeb harva otse kliendiga. Tavalised on ketid, mis sisaldavad Reverse Proxy, TLS‑Termination, WAF või API‑Gateway’d. Sellest tulenevad praktilised tähelepanekud:

    • Remote IP: Ärge uskuge pimesi X-Forwarded-For-päist. Võtke see arvesse ainult usaldusväärsetest proksidest ja muul juhul kasutage otsest socket‑IP‑d. Operatsioonijuhendis peaks olema kirjas, millised hüpped on „trusted“.
    • Timeouts: Kui proksil on 30 sekundi timeout, aga teie backend vajab 2 minutit, tekivad ghost‑päringud. Määrake kogu ahelas ajapiirangud järjepidevalt ja otsustage: sünkroonne päring või job‑pattern (202 Accepted + staatus‑lõpp‑punkt).
    • Correlation-ID: Lisage Correlation‑ID vastuse päistesse, et administraatorid saaksid seda logidest ja kliendipoolselt kokku viia. Kui gateway genereerib oma Request‑ID‑sid: logige ja siduge mõlemad ID‑d.
    • Fehlertexte: Tootmiskeskkonnas ärge avaldage sisemisi detaile. Debug‑detailid lubage ainult kontrollitud tingimustes (staging/feature‑flag) ja eelistatult ainult logides.

    Paigutus: miks RemObjects SDK siin eelis olla võib

    Delphi‑ökosüsteemis ehitatakse REST‑servereid tihti kergemate raamistikudega (nt minimalistlikud HTTP‑routerid). RemObjects SDK näitab oma tugevust eriti siis, kui teil on olemas või on vaja mitmekihilist arhitektuuri:

    • Selged teenusepiirid: teenusemeetodid on eksplicitseeritud, kontraktid (contracts) on versioonitavad.
    • Transpordid ja serialiseerimine: saate rääkida JSON‑iga, aga ka teiste sõnumiformaatidega (sõltuvalt ülesseadest), ilma äriloogikat segamata.
    • Haldus: hostimisvõimalused ja integreerumine olemasolevatesse Windows- ja Linux-services on planeeritavad, sh puhtad roll‑out’id.

    Näidatud lähenemine täidab need lüngad, mis igapäevatöös sageli puuduvad: ühtlustatud veao objektid, deterministlik versioonihaldus ja korreleeritav logimine. Eriti individuaalse ettevõtteklassiga tarkvara pika elutsükli puhul säästate sellega aega nii uuenduste juures kui ka välish-süsteemide integreerimisel.

    Kokkuvõte: kas vaev tasub end ära — ja kus läheb lähenemine üleliigseks?

    Lisandväärtus tekib siis, kui teie REST‑liides ei lihtsalt „tööta“, vaid on püsivalt opereeritav: stabiilsed JSON‑lepingud, versioonihaldus ilma URL‑kaose, selged vead ja debug’imine ilma oletamiseta. Just siin on Context, Correlation‑ID ja keskne Exception‑Mapping RemObjects SDK‑ga tugevad.

    Kasutuspiirid: Kui teil on ainult üksik, lühiajaline lõpp‑punkt ilma integratsioonipartneriteta, võib Media‑Type‑versioonimine kiiresti tunduda üleinsenerituna. Snapshot‑logimine on mõistlik ainult siis, kui rakendate andmete peitmist ja aktiveerimist distsiplineeritult. Ja: kui teie proksi‑pinu „optimeerib“ või eemaldab päiseid, peate esmalt infrastruktuuri korrastama, vastasel juhul debugite vale kihti.

    Kui moderniseerite olemasolevat Delphi‑serverimaastikku või peate protsessile lähedast tarkvaralahendust puhtalt integreerima ERP/DMS/CRM‑iga, on need mehhanismid tihti just see erinevus „töötab testis“ ja „töötab tootmises“ vahel.

    Tehnilises kontekstis mängivad ka Delphi REST-API ja REST-Server ning Remobjects Sdk Delphi olulist rolli, kui integratsioonid, andmevood ja edasiarendus peavad omavahel nõuetekohaselt toimima.

    Arutada projekti või moderniseerimisettevõtmist koos Net-Base.

    järgmine samm

    Kui teemast saab reaalne projekt, tuleks arhitektuuri, olemasolevat keskkonda ja ekspluatatsiooni varakult koos vaadelda.

    Me ei toeta ainult üksikute küsimuste lahendamist, vaid ka siis, kui lähtekoodilõikudest, pärandsüsteemidest või portaalikontseptsioonidest peab saama usaldusväärne ettevõtteprojekt.

    • Olemasolev olukord, sihtpilt ja tehnilised riskid hinnatakse üheskoos.
    • REST, andmejuurdepääs, portaalid ja juurutamine ei lükata hilisemateks tagajärgedeks edasi.
    • Te näete varakult, milline tee on majanduslikult ja operatiivselt jätkusuutlik.

    Jaga postitust

    Jaga seda postitust otse

    LinkedIn, X, XING, Facebook, WhatsApp ja e-post on kohe saadaval. Instagrami jaoks valmistame lingi ja lühiteksti otse ette.

    e-post

    Instagram avatakse uues vahekaardis. Link ja lühitekst kopeeritakse eelnevalt lõikepuhvrisse.