Net-Base Rivista

09.06.2026

REST API con RemObjects SDK: versionare e eseguire il debug degli endpoint JSON in modo pulito (Delphi frammenti di codice sorgente)

Come costruire con RemObjects SDK in Delphi un'API REST che non fallisca in produzione: contratti JSON stabili, versionamento senza proliferazione di URL, Correlation-ID attraverso tutti i livelli, mappatura centralizzata degli errori, Snapshot-Logging per casi di debug critici e indicazioni pratiche...

09.06.2026

Dal tema della rivista alla pratica di progetto

Pagine di servizi e tecniche correlate all'articolo

Perché „REST API con RemObjects SDK“ nella pratica spesso si decide ai margini

Una REST API con RemObjects SDK raramente si giudica sul servizio “Hello World”, ma nei punti in cui operatività, legacy e integrazione si scontrano: versionamento senza interruzioni, comportamento degli errori coerente su tutti gli endpoint, debugging riproducibile nelle catene di proxy e la capacità di correlare in modo univoco le richieste in caso di problemi.

RemObjects SDK fornisce molta infrastruttura: servizi, formati di messaggi, serializzazione, hosting (p.es. come Windows- e Linux-Services o dietro IIS/Reverse Proxy) e punti definiti per gestire gli errori in modo centralizzato. Quello che però manca spesso negli ambienti software aziendali consolidati è un contratto applicato in maniera coerente: quali campi JSON sono stabili? Come segnaliamo gli errori? Come riconosciamo una richiesta quando ha attraversato load balancer, TLS-termination e più livelli di backend?

Il seguente approccio (inclusi snippet di Delphi) propone una linea robusta per RemObjects SDK: versionare i contratti JSON, imporre la Correlation-ID (Request-ID per il tracciamento), tradurre le eccezioni in HTTP-Status e oggetti di errore JSON e, nel contempo, non contrapporre debugging e operatività. In aggiunta esaminiamo i casi limite che si verificano regolarmente in ambienti reali: threading sul server, accessi al database con BDE-sostituzione con connessione nativa, header dei proxy, timeout e payload client «sporchi».

Decisione architetturale: versionamento tramite Media Type invece che tramite URL

Molte API versionano tramite percorsi come /v1/. È pragmatico, ma in integrazioni a lunga durata (p.es. collegamenti ERP/DMS/CRM) spesso conduce a duplicazione degli URL, rotte duplicate, test doppi e alla domanda «Quale versione stiamo effettivamente usando?» nei manuali operativi.

Un’alternativa è il versionamento tramite Media Type (content negotiation). Il client invia p.es. Accept: application/vnd.company.order+json;v=2. Il server legge la versione in modo deterministico e adatta il comportamento del contratto/DTO. Questo funziona nelle catene di proxy e cache, se gli header vengono inoltrati correttamente. Per gli amministratori è inoltre facilmente verificabile: una richiesta si può riprodurre con Curl/Postman senza che gli URL cambino.

RemObjects SDK non è “REST-puristisch”, ma un framework di servizio pragmatico. Proprio per questo la variante basata sul media type ha senso: potete mantenere endpoint stabili e comunque evolvere i contratti. È importante valutare la versione sempre, decidere centralmente in un punto e trasferire il risultato nel contesto del servizio.

Quando fallisce la variante basata sull’header Accept?

Nella pratica ci sono tre punti di rottura tipici da affrontare in anticipo:

  • Proxy-Policies: alcune regole di Reverse Proxy/WAF normalizzano o filtrano gli header Accept. In quel caso la vostra API ricade silenziosamente sul valore di default. Soluzione: verificare esplicitamente le regole del proxy, eventualmente ricorrere a X-Api-Version come fallback.
  • Client-Libraries: alcune librerie HTTP impostano header Accept propri e sovrascrivono i valori. Soluzione: supportare la versione del contratto anche come parametro di query opzionale (solo come fallback), oppure parsare l’header Accept sul server in modo tollerante.
  • Caching: Se il Response-Caching è in uso, la cache deve variare rispetto a Accept (Vary: Accept), altrimenti fornisce la versione 1 ai client della versione 2. Soluzione: impostare consapevolmente Vary, oppure disabilitare il caching a livello di API.
  • Snippet sorgente: contesto della richiesta, Correlation-ID, versione e mappatura degli errori

    Il codice è volutamente pensato per potersi integrare in progetti server RemObjects esistenti: un piccolo livello di contesto, un parser per la versione dell’API (da Accept), un meccanismo di Correlation-ID e un mapping centrale delle eccezioni. Termini:

    • Correlation-ID: ID univoco per ogni richiesta, che ricompare nella response e viene riferito nei log.
    • Exception-Mapping: Traduzione delle eccezioni interne Delphi in oggetti di errore stabili e gestibili dal client (incl. stato HTTP).
    • Contract-Version: Versione del contratto JSON, che controlla comportamento e campi.
    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;
    // Atteso, per es.: 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;
    // In produzione nessun dettaglio interno, nessuna informazione SQL, nessun percorso.
    // Per debug/stage è possibile estendere questo comportamento tramite configurazione.
    begin
      if E is EApiError then
        Exit(E.Message);
    
      if E is EArgumentException then
        Exit('Parametri non validi.');
    
      Exit('Errore interno.');
    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.

    Scopo: contesto della richiesta stabile invece di „da qualche parte nel Threadlocal“

    Lo snippet separa intenzionalmente: TApiContext è lo stato minimo che volete propagare. In RemObjects SDK molto passa tramite il contesto Server/Channel. In progetti eterogenei (p.es. thread worker aggiuntivi, code DB, job in background) il passaggio esplicito è spesso più robusto degli implicit Threadlocal, perché rende più visibili la concorrenza e i cambi di contesto.

    Vincoli: La variante basata sull’header Accept presuppone che il vostro reverse proxy (nginx, IIS ARR, Traefik) inoltri l’header senza modificarlo. In alcuni ambienti gli header Accept „non convenzionali“ vengono filtrati o aggregati.

    Insidie: Il versionamento tramite Accept è valido quanto i vostri test. Se i client usano librerie che sovrascrivono l’header Accept, un’API può improvvisamente ricadere sul valore predefinito. Per client legacy un fallback predefinito ha senso, ma deve essere visibile nel monitoring (p.es. un avviso di log „Version defaulted“).

    Varianti: Se preferite fare il versionamento tramite X-Api-Version: il parser è identico, cambia solo la sorgente. Dal punto di vista dei gateway questo a volte è più semplice da controllare.

    Integrazione in RemObjects SDK: Correlation-ID e Exception-Mapping all’ingresso del servizio

    L’effetto reale nasce quando applicate la meccanica in modo coerente al perimetro del vostro server: una volta leggere le informazioni dagli header all’ingresso della request, una volta tradurre le eccezioni in una response stabile all’uscita. A seconda dell’hosting (p.es. RO-HTTP-Server, IIS-Hosting, servizi self-hosted Windows-/Windows- e Linux-Services) i punti di hook concreti differiscono; il principio resta lo stesso: costruire il contesto, invocare la business logic, mappare centralmente le eccezioni.

    Nei progetti RemObjects si lavora spesso direttamente per metodo di servizio. Questo scala all’inizio, ma fallisce in esercizio: ogni metodo costruisce logging e gestione errori a modo suo. Un taglio netto è una base di servizio o un dispatcher che standardizzi il comportamento.

    Flusso pratico (volutamente breve e vicino all’implementazione)

    1. Leggere la Correlation-ID dall’header della request X-Correlation-ID; se manca, generarla lato server (p.es. GUID).
    2. Leggere la versione del contratto dall‘Accept (o dall‘X-Api-Version).
    3. Registrare l’inizio della request: metodo, percorso, Correlation-ID, IP remoto, avviare la misurazione della durata.
    4. Eseguire la business logic; incapsulare gli accessi al DB in modo transazionale quando possibile.
    5. Intercettare le eccezioni: determinare lo status HTTP, creare un oggetto errore JSON, impostare l’header di response X-Correlation-ID.
    6. Registrare la fine della request: status, durata, eventualmente codice errore.

    Threading nel server: perché la Correlation-ID senza disciplina del contesto diventa inutile

    Un caso limite comune in Delphi: il metodo di servizio innesca lavoro asincrono (p.es. generazione report, import, push in un DMS). In quel caso il thread originario della request non è più quello che scriverà le righe di log in seguito. Se la Correlation-ID è nota solo „all’inizio“, la tracciabilità si disintegra.

    Regola pragmatica: tutto ciò che non rimane strettamente nel thread della request riceve il contesto passato esplicitamente. Anche se sembra generare più liste di parametri, ripaga. In alternativa si può lavorare con un oggetto di contesto chiaramente definito, che viene passato intenzionalmente ai worker (invece di variabili globali o singleton nascosti).

    Punti critici tipici in server RemObjects-/Delphi:

    • DB-Connections pro Thread: BDE-Ablosung mit nativer Anbindung-Verbindungen non sono automaticamente thread-safe e condivisibili. Un pool di connessioni o una connessione per thread è spesso più sensato rispetto a „una Connection globale“.
    • Transaktionsgrenzen: Se all’interno di una request ci sono più passaggi collegati, la transazione deve rimanere nella stessa unità logica. Lavoro asincrono non deve proseguire „inavvertitamente“ nella stessa transazione.
    • Cancellation: Se il client interrompe (timeout del proxy, browser chiuso), il server spesso continua l’elaborazione. Considerate consapevolmente se il lavoro in background abbia ancora senso in quel caso.

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

    Nei progetti di integrazione un mapping degli errori accurato è più che un dettaglio cosmetico. Determina se un interlocutore (connettore ERP, job ETL, portale clienti) può reagire correttamente. Alcune linee guida pratiche che si sono dimostrate valide in ambienti Delphi/RemObjects:

    • 400 Bad Request: Validazione, parametri mancanti/non validi, JSON non parsabile. Importante: la risposta dovrebbe restare stabile anche se il body è corrotto.
    • 401/403: Separare autenticazione e autorizzazione. 401 significa „identità mancante/ invalida“, 403 „identità ok, ma accesso vietato“.
    • 404: La risorsa non esiste. Attenzione alla security: non sempre è opportuno rivelare se qualcosa esiste o meno.
    • 409 Conflict: Conflitto di dominio (es. conflitto di versione, „lo stato non consente questa azione“, violazione di chiave unica se rilevante dal punto di vista funzionale).
    • 422 Unprocessable Content: Quando la sintassi è corretta ma la validazione funzionale fallisce (non tutti i team usano 422, ma spesso è più chiaro rispetto a 400).
    • 500: Tutto ciò che non riuscite a classificare in modo preciso. Include anche „DB giù“, „timeout“, „Unhandled Exception“.

    Accorgimento specifico per Delphi: molti errori di DB emergono come eccezioni generiche. Vale la pena, nel livello di accesso ai dati, rilevare situazioni note e convertirle in EApiError. Importante: non riportare frammenti SQL o nomi interni di tabelle/colonne nei messaggi al client. Questi dettagli devono finire nei log, non nella response.

    Debugging-Kniff: reproduzierbare Fehler durch „Contract Snapshot“

    Inusuale, ma estremamente utile in esercizio: salvare, in caso di errori (o selettivamente per certe Correlation-ID), un „snapshot“ composto da header della request + body della request in un file di spool per il debug. Non si tratta di logging permanente (privacy/volume), ma di uno strumento controllato per riprodurre casi difficili vicini all’ambiente di produzione.

    Importante: uno snapshot non deve mai persistere in modo non filtrato header di autenticazione, token o dati personali. Nella pratica ciò significa: mascheramento e attivazione solo tramite feature-flag o whitelist (es. solo per certe ID di correlazione, finestre temporali brevi).

    Saubere Umsetzung in der Praxis: Maskieren statt Weglassen

    Nelle integrazioni reali i campi „critici“ sono spesso quelli necessari per il debug (es. identificatori). Invece di omettere a priori è preferibile mascherare: parzializzare i token, mantenere solo il dominio delle e-mail, mostrare solo le ultime cifre dell’IBAN. In questo modo il caso rimane riproducibile senza distribuire dati non necessari nel filesystem. In aggiunta lo snapshot dovrebbe essere chiaramente etichettato come artefatto di debug e avere un tempo di conservazione definito.

    Sicurezza e operatività: inoltro degli header, catene di proxy e timeout

    Una REST API raramente termina direttamente sul client. Tipico sono catene di reverse proxy, terminazione TLS, WAF o API gateway. Da ciò derivano punti pratici:

    • Remote IP: non fate affidamento cieco su X-Forwarded-For. Accettatelo solo da proxy fidati e altrimenti utilizzate l’IP della socket diretta. Nei manuali operativi deve essere indicato quali hop sono „trusted“.
    • Timeouts: se un proxy ha 30 secondi ma il vostro backend richiede 2 minuti, creerete richieste fantasma. Impostate i timeout in modo coerente lungo tutta la catena e decidete: richiesta sincrona o pattern a job (202 Accepted + endpoint di stato).
    • Correlation-ID: inserite la Correlation-ID negli header di risposta, in modo che gli amministratori possano ricongiungerla tra log e lato client. Se un gateway usa proprie request-ID: registrate e mappate entrambe le ID.
    • Fehlertexte: in produzione nessun dettaglio interno. Dettagli di debug solo in modo controllato (Stage/Feature-Flag) e, in caso di dubbio, solo nei log.

    Einordnung: Warum RemObjects SDK hier im Vorteil sein kann

    Negli ecosistemi Delphi i REST-Server sono spesso costruiti con framework più leggeri (ad es. router HTTP minimalisti). RemObjects SDK esprime i suoi punti di forza quando avete già, o avete bisogno di, un’architettura a più livelli:

    • Confini di servizio chiari: i metodi di servizio sono espliciti, i contratti sono versionabili.
    • Trasporti e serializzazione: potete comunicare in JSON, ma anche usare altri formati di messaggio (a seconda della configurazione), senza contaminare la logica di dominio.
    • Operatività: opzioni di hosting e integrazione in servizi Windows- e Linux-Services esistenti sono pianificabili, inclusi rollout ordinati.

    L’approccio mostrato integra le parti che spesso mancano nella pratica quotidiana: oggetti errore unificati, versioning deterministico e logging correlabile. Specialmente per software aziendale su misura con lunghi cicli di vita, questo consente di risparmiare tempo negli aggiornamenti e nell’integrazione di sistemi esterni.

    Fazit: Lohnt sich der Aufwand – und wo kippt der Ansatz?

    Il valore aggiunto emerge quando la vostra interfaccia REST non si limita a „funzionare“, ma è gestibile nel lungo periodo: contratti JSON stabili, versioning senza proliferazione di URL, errori tracciabili e debug senza indovinelli. Proprio in questi ambiti l’approccio con Context, Correlation-ID e mappatura centralizzata delle eccezioni in RemObjects SDK è efficace.

    Limiti d’impiego: se avete solo un singolo endpoint effimero senza partner di integrazione, il versionamento via Media-Type può apparire subito come overengineering. Anche lo snapshot-logging è sensato solo se implementate disciplinatamente la redaction e il meccanismo di attivazione. E: se il vostro stack di proxy „ottimizza“ o rimuove gli header, dovete prima raddrizzare l’infrastruttura, altrimenti debuggherete lo strato sbagliato.

    Se dovete modernizzare una landscape di server Delphi esistente o integrare in modo pulito una soluzione software di prossimità ai processi in ERP/DMS/CRM, questi meccanismi sono spesso la differenza tra „funziona nel test“ e „funziona in produzione“.

    Nell’ambito tecnico rivestono inoltre un ruolo importante Delphi REST-API e REST-Server e Remobjects Sdk Delphi, quando integrazioni, flussi di dati e l’evoluzione continua devono interagire in modo coerente.

    Discutere progetto o intervento di modernizzazione con Net-Base.

    Passo successivo

    Quando un tema diventa un progetto reale, architettura, sistemi esistenti e gestione operativa dovrebbero essere considerati insieme fin dalle fasi iniziali.

    Non forniamo solo supporto per questioni isolate, ma anche quando da frammenti di codice sorgente, tematiche legacy o idee di portale deve nascere un progetto aziendale solido.

    • Stato attuale, stato obiettivo e rischi tecnici vengono valutati insieme.
    • REST, l'accesso ai dati, i portali e il rollout non vengono rinviati a fasi successive.
    • Vede in anticipo quale percorso è economicamente e operativamente sostenibile.

    Condividi il post

    Condividi direttamente questo articolo

    LinkedIn, X, XING, Facebook, WhatsApp e e-mail sono immediatamente disponibili. Per Instagram prepariamo direttamente il link e un breve testo.

    E-mail

    Instagram si apre in una nuova scheda. Il link e il breve testo vengono copiati prima negli appunti.