Net-Base Revistă

09.06.2026

REST API cu RemObjects SDK: versionarea și depanarea curată a endpoint-urilor JSON (Delphi fragmente de cod sursă)

Cum să construiești cu RemObjects SDK în Delphi o REST API care nu se rupe în producție: contracte JSON stabile, versionare fără proliferare de URL-uri, Correlation-ID prin toate straturile, mapare centralizată a erorilor, înregistrare snapshot pentru cazuri dificile de depanare și indicații practice...

09.06.2026

De la tema din revistă la practica în proiecte

Pagini relevante de servicii și pagini tehnice pentru articol

De ce „REST API cu RemObjects SDK” decide în practică adesea în punctele critice

Un REST API cu RemObjects SDK nu câștigă sau nu pierde de obicei la serviciul „Hello World”, ci în locurile în care operarea, sistemele legacy și integrarea se ciocnesc: versionare fără opriri, comportament de eroare consecvent pe toate end-point-urile, debugging reproducibil în lanțuri de proxy și capacitatea de a corela în mod univoc request-urile în caz de problemă.

RemObjects SDK aduce pentru aceasta multă infrastructură: servicii, formate de mesaje, serializare, hosting (de ex. ca Windows- și Linux-Services sau în spatele IIS/Reverse Proxy) și puncte bine definite pentru tratarea centralizată a erorilor. Ce lipsește frecvent în peisajele de business software crescute este însă un contract aplicat consecvent: Care câmpuri JSON sunt stabile? Cum semnalizăm erorile? Cum recunoaștem un request când a trecut prin load balancer, TLS-Termination și mai multe straturi backend?

Abordarea următoare (inclusiv Delphi-snippet-uri) arată o linie robustă pentru RemObjects SDK: versionarea contractelor JSON, împunerea unei Correlation-ID (Request-ID pentru urmărire), translatarea excepțiilor în coduri de stare HTTP și obiecte JSON de eroare și evitarea opoziției între debugging și operare. În plus analizăm cazurile-limită care apar frecvent în medii reale: threading pe server, acces la baze de date în contextul înlocuirii BDE cu legături native, headerele proxy, timeouts și payload-uri client „murdare”.

Decizie arhitecturală: versionare prin Media Type în loc de URL

Multe API-uri versionează prin rute precum /v1/. Este pragmatic, dar în integrări care rulează pe termen lung (de ex. conectări ERP/DMS/CRM) conduce adesea la duplicare de URL-uri, rute duplicate, teste duplicate și la întrebarea „Ce versiune folosim de fapt?” în manualele de operare.

O alternativă este versionarea prin Media Type (Content Negotiation). Clientul trimite, de ex., Accept: application/vnd.company.order+json;v=2. Serverul citește versiunea în mod determinist și adaptează comportamentul Contract/DTO. Aceasta funcționează în lanțuri de proxy și cache dacă headerele sunt propagate corect. Pentru administratori este, de asemenea, ușor verificabil: un request poate fi reprodus cu Curl/Postman fără a schimba URL-urile.

RemObjects SDK nu este „REST-purist”, ci un framework de servicii pragmatic. Tocmai de aceea varianta prin Media Type merită luată în considerare: puteți păstra end-point-uri stabile și, în același timp, evolua contractele. Important este să evaluați versiunea întotdeauna, să decideți centralizat într-un singur loc și să preluați rezultatul în contextul serviciului.

Când cedează varianta cu Accept-Header?

În practică există trei puncte tipice de rupere pe care ar trebui să le adresați dinainte:

  • Proxy-Policies: Unele reguli de Reverse Proxies/WAF normalizează sau filtrează Accept-Header. Atunci API-ul dvs. revine silențios la valoarea default. Soluție: verificați explicit regulile proxy-ului, eventual recurgeți la X-Api-Version ca alternativă.
  • Client-Libraries: Unele librării HTTP stabilesc propriile Accept-Header și suprascriu valorile. Soluție: suportați versiunea contractului și ca parametru de interogare opțional (doar ca fallback), sau parsați Accept-Header-ul pe server într-o manieră tolerantă.
  • Caching: Dacă există Response-Caching, cache-ul trebuie să varieze în funcție de Accept (Vary: Accept), altfel va livra versiunea 1 clienților care așteaptă versiunea 2. Soluție: setați în mod explicit Vary sau dezactivați caching-ul la nivelul API.
  • Fragment de cod sursă: Request-Context, Correlation-ID, versiune și mapare a erorilor

    Codul este conceput intenționat astfel încât să poată fi integrat în proiectele server RemObjects existente: un strat mic de context, un parser pentru versiunea API (din Accept), un mecanism Correlation-ID și un mapping central al excepțiilor. Termeni:

    • Correlation-ID: ID unic pentru fiecare cerere, care apare din nou în răspuns și este referențiată în jurnale.
    • Exception-Mapping: Traducerea excepțiilor interne Delphi în obiecte de eroare stabile, procesabile de client (incl. status HTTP).
    • Contract-Version: Versiunea contractului JSON care controlează comportamentul și câmpurile.
    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.

    Scop: Context stabil al cererii în loc de „undeva în threadlocal”

    Fragmentul separă în mod intenționat: TApiContext este starea minimă pe care doriți s-o transmiteți. În RemObjects SDK multe lucruri se bazează pe contextul Server-/Channel. În proiecte eterogene (de ex. thread-uri worker suplimentare, cozi DB, joburi de fundal) transmiterea explicită este adesea mai robustă decât threadlocal-urile implicite, deoarece face concurența și schimbările de context mai vizibile.

    Condiții cadru: Variantei cu headerul Accept îi este necesar ca Reverse Proxy-ul dvs. (nginx, IIS ARR, Traefik) să retransmita headerul nemodificat. În unele medii, header-ele Accept „neobișnuite” sunt filtrate sau agregate.

    Capcane: Versionarea prin Accept este la fel de bună ca testele dvs. Dacă clienții folosesc biblioteci care suprascriu Accept, o API poate reveni brusc la valoarea implicită. Pentru clienți legacy un fallback implicit este rezonabil, dar trebuie să fie vizibil în monitoring (de ex. avertisment în log „Version defaulted”).

    Variante: Dacă preferați versionarea prin X-Api-Version: parserul este identic, doar sursa este un alt header. Din perspectiva gateway-urilor, asta e uneori mai ușor de controlat.

    Integrarea în RemObjects SDK: Correlation-ID și maparea excepțiilor la intrarea serviciului

    Efectul real apare când aplicați mecanica consecvent la marginea serverului: o singură dată la intrarea request-ului citiți din header, o singură dată la ieșirea excepției traduceți într-un răspuns stabil. În funcție de hosting (de ex. RO-HTTP-Server, IIS-Hosting, servicii Windows-/Windows- și Linux-Services auto-gazduite) punctele concrete de hook diferă; principiul rămâne același: construiți Context-ul, apelați logica de business, mapați excepțiile centralizat.

    În proiectele RemObjects se lucrează frecvent direct per metodă de serviciu. Asta scalează bine la început, dar cedează în exploatare: fiecare metodă implementează logging și tratarea erorilor diferit. O separare clară este o bază de serviciu sau un dispatcher care standardizează.

    Procedură practică (intenționat scurtă și orientată spre implementare)

    1. Citiți Correlation-ID din headerul request-ului X-Correlation-ID; dacă lipsește, generați-l pe server (de ex. GUID).
    2. Citiți Contract-Version din Accept (sau din X-Api-Version).
    3. Logați începutul request-ului: metodă, cale, Correlation-ID, IP remote, porniți măsurarea duratei.
    4. Executați logica de business; încapsulați accesul DB, pe cât posibil, în tranzacții.
    5. Prindeți excepțiile: determinați statusul HTTP, generați obiectul JSON de eroare, setați headerul de răspuns X-Correlation-ID.
    6. Logați sfârșitul request-ului: status, durată, eventual cod de eroare.

    Threading pe server: De ce Correlation-ID devine inutilă fără disciplină de context

    Un Delphi-caz de margine frecvent: metoda serviciului declanșează muncă asincronă (de ex. generare de rapoarte, import, push într-un DMS). Atunci thread-ul inițial al request-ului nu mai este cel care scrie ulterior liniile de log. Dacă Correlation-ID este cunoscută doar „la început”, trasabilitatea se destramă.

    Regulă pragmatică: Tot ce nu rămâne strict în thread-ul request-ului trebuie să primească Contextul transmis explicit. Chiar dacă asta înseamnă liste de parametri mai lungi, se amortizează. Alternativ, lucrați cu un obiect de context clar definit, care este în mod deliberat transmis către worker (în loc de variabile globale sau singletons ascunse).

    Puncte tipice de cedare în servere RemObjects-/Delphi:

    • Conexiuni DB pe fir de execuție: BDE-Ablosung mit nativer Anbindung-conexiunile nu sunt în mod automat partajabile între fire de execuție în condiții de siguranță. Un pool de conexiuni sau câte o conexiune per fir de execuție este adesea mai potrivit decât „o conexiune globală”.
    • Limite ale tranzacției: Dacă, în cadrul unei cereri, aveți mai mulți pași care aparțin aceleiași operațiuni, tranzacția trebuie să rămână în aceeași unitate logică. Lucrul asincron nu trebuie să continue „din greșeală” în aceeași tranzacție.
    • Anulare: Dacă clientul întrerupe (timeout la proxy, browser închis), serverul continuă adesea să ruleze. Decideți în mod conștient dacă lucrările de fundal mai sunt justificate.

    Acces la date și coduri de eroare: 409 nu este „tot un 500”

    În proiectele de integrare, un mapping curat al erorilor este mai mult decât cosmetică. El decide dacă interlocutorul (ERP-Connector, ETL-Job, portalul clienților) poate reacționa corect. Câteva linii directoare practice care s-au dovedit utile în mediile Delphi/RemObjects:

    • 400 Bad Request: Validare, parametri lipsă/invalidi, JSON imposibil de parsare. Important: Răspunsul trebuie să rămână stabil chiar dacă corpul cererii este corupt.
    • 401/403: Separați autentificarea de autorizare. 401 înseamnă „fără/identitate invalidă”, 403 „identitatea e ok, dar interzis”.
    • 404: Resursa nu există. Atenție la securitate: nu dezvăluiți întotdeauna dacă ceva există.
    • 409 Conflict: Conflict de natură funcțională (de ex. conflict de versiune, „starea nu permite această acțiune”, încălcarea unei chei unice dacă are relevanță funcțională).
    • 422 Unprocessable Content: Când sintactic totul este OK, dar validarea de business eșuează (nu toate echipele folosesc 422, dar este adesea mai clar decât 400).
    • 500: Tot ce nu poate fi clasificat curat. Include și „DB down”, „Timeout”, „Unhandled Exception”.

    Truc specific Delphi: Multe erori de DB apar ca excepții generice. Merită să verificați în stratul de acces la date situații cunoscute și să le transformați în EApiError. Important: Nu transferați fragmente SQL sau nume interne de tabele/coloane în mesajul către client. Aceste detalii aparțin jurnalului, nu răspunsului.

    Truc pentru debugging: erori reproducibile prin „Contract Snapshot”

    Nefirească, dar extrem de util în exploatare: salvați, la erori (sau țintit pentru anumite Correlation-IDs), un „snapshot” din antetele cererii + corpul cererii într-un fișier Debug-Spool. Nu este logging permanent (protecția datelor/volum), ci un instrument controlat pentru a reproduce cazuri greu de reprodus din apropierea producției.

    Important: Un snapshot nu trebuie niciodată să persiste nefiltrat Auth-Header, token-uri sau date cu caracter personal. În practică asta înseamnă: Redaction (mascare) și activare doar prin feature-flag sau whitelist (de ex. doar pentru anumite Correlation-IDs, perioade scurte).

    Implementare practică curată: mascare în loc de omiterea datelor

    În integrările reale, tocmai câmpurile „critice” sunt adesea cele de care ai nevoie pentru debugging (de ex. identificatori). În locul excluderii generalizate, mascare: înlocuiți parțial token-urile, păstrați doar domeniul dintr-un e‑mail, IBAN doar ultimele cifre. Astfel cazul rămâne reprodu­cibil, fără a distribui date inutile în sistemul de fișiere. În plus, snapshot-ul ar trebui marcat clar ca artefact de debug și să aibă o perioadă de păstrare definită.

    Securitate și operare: propagarea antetelor, lanțuri de proxy și timeout-uri

    O API REST rar se termină direct la client. Tipic sunt lanțuri formate din reverse proxy, TLS-Termination, WAF sau API-Gateway. Din acestea rezultă câteva puncte practice:

    • Remote IP: Nu vă bazați orbește pe X-Forwarded-For. Preluați-l doar de la proxy-urile de încredere; în caz contrar, folosiți IP-ul socket-ului direct. În manualele de operare trebuie precizat care hop-uri sunt „trusted”.
    • Timeouts: Dacă proxy-ul are 30 de secunde, iar backend-ul dumneavoastră are nevoie de 2 minute, veți genera cereri fantomă. Stabiliți timeout-urile consecvent de-a lungul lanțului și decideți: request sincron sau pattern Job (202 Accepted + endpoint de stare).
    • Correlation-ID: Includeți Correlation-ID în header-urile de răspuns, astfel încât adminii să o poată corela din loguri și din partea clientului. Dacă un gateway folosește propriile Request-IDs: logați și mapați ambele ID-uri.
    • Mesaje de eroare: În producție nu afișați detalii interne. Detaliile de debug doar controlat (Stage/Feature-Flag) și, în caz de dubiu, doar în log.

    Context: De ce RemObjects SDK poate fi avantajos aici

    În ecosistemele Delphi serverele REST-Server sunt adesea construite cu framework-uri mai ușoare (de ex. routere HTTP minimaliste). RemObjects SDK își joacă puterea atunci când aveți sau aveți nevoie de o arhitectură multistrat:

    • Granițe clare ale serviciilor: Metodele serviciilor sunt explicite, contractele pot fi versionate.
    • Transporturi și serializare: Puteți folosi JSON, dar și alte formate de mesaj (în funcție de configurare), fără a amesteca logica de domeniu.
    • Operare: Opțiunile de hosting și integrarea în Windows- și Linux-servicii existente sunt planificabile, inclusiv rollout-uri curate.

    Abordarea prezentată completează aceasta cu părțile care lipsesc adesea în practică: obiecte de eroare uniforme, versionare deterministă și înregistrări corelabile. Mai ales în cazul software-ului enterprise personalizat cu cicluri de viață lungi, economisiți astfel timp la update-uri și la integrarea cu sisteme externe.

    Concluzie: Merită efortul — și unde devine abordarea nepotrivită?

    Valoarea adăugată apare atunci când interfața REST nu doar „funcționează”, ci este operabilă pe termen lung: contracte JSON stabile, versionare fără proliferare de URL-uri, erori urmăribile și debugging fără ghicit. Tocmai aici abordarea cu Context, Correlation-ID și mapping centralizat al excepțiilor în RemObjects SDK este puternică.

    Limite de aplicare: Dacă aveți doar un singur endpoint, efemer, fără parteneri de integrare, versionarea pe Media-Type poate părea rapid overengineering. De asemenea, Snapshot-Logging are sens doar dacă implementați disciplinat redaction și activare. Și: dacă stack-ul dvs. de proxy-uri „optimizează“ sau elimină headere, trebuie mai întâi să aliniați infrastructura, altfel debugați stratul greșit.

    Dacă modernizați o platformă de servere Delphi existentă sau trebuie să integrați curat o soluție software apropiată de proces în ERP/DMS/CRM, aceste mecanisme sunt adesea diferența între „funcționează în test” și „funcționează în producție”.

    La nivel funcțional, Delphi REST-API și REST-Server și Remobjects Sdk Delphi joacă, de asemenea, un rol important, atunci când integrarea, fluxurile de date și dezvoltarea ulterioară trebuie să funcționeze coerent.

    Discutați proiectul sau demersul de modernizare cu Net-Base.

    Pasul următor

    Dacă un subiect devine un proiect real, arhitectura, starea existentă și operarea ar trebui analizate împreună încă din faza incipientă.

    Nu oferim sprijin doar pentru întrebări punctuale, ci și atunci când fragmente de cod sursă, probleme legacy sau idei de portal trebuie transformate într-un proiect robust la nivel de companie.

    • Situația curentă, starea țintă și riscurile tehnice sunt evaluate împreună.
    • REST, accesul la date, portalurile și implementarea nu sunt amânate pentru etape ulterioare.
    • Veți vedea din timp care opțiune este viabilă din punct de vedere economic și operațional.

    Partajează postarea

    Distribuiți această postare direct

    LinkedIn, X, XING, Facebook, WhatsApp și E-Mail sunt disponibile imediat. Pentru Instagram pregătim direct linkul și textul scurt.

    E-mail

    Instagram se deschide într-o filă nouă. Linkul și textul scurt se copiază în prealabil în clipboard.