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 fa la differenza ai margini
Un REST API con RemObjects SDK raramente viene giudicata dal servizio „Hello World“, ma piuttosto nei punti in cui operativit , componenti legacy e integrazione si scontrano: versioning senza interruzioni, comportamento degli errori coerente su tutti gli endpoint, debugging riproducibile in catene di proxy e la capacit di correlare in modo univoco le richieste in caso di problemi.
RemObjects SDK fornisce a questo scopo molta infrastruttura: servizi, formati di messaggi, serializzazione, hosting (ad es. come Windows- e Linux-Services o dietro IIS/Reverse Proxy) e punti definiti per gestire gli errori centralmente. Ci che spesso manca nelle architetture di business software consolidate un contratto applicato con coerenza: quali campi JSON sono stabili? Come segnaliamo gli errori? Come riconosciamo una richiesta quando ha attraversato load balancer, terminazione TLS e pi strati di backend?
L’approccio seguente (inclusi snippet di Delphi) mostra una linea robusta per RemObjects SDK: versionare i contratti JSON, imporre la Correlation-ID (Request-ID per il tracciamento), tradurre le eccezioni in status HTTP e oggetti di errore JSON senza contrapporre debugging e operativit . Inoltre esaminiamo casi limite che si presentano regolarmente in ambienti reali: threading nel server, accessi al database con BDE-Ablosung con collegamento nativo, 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/.
Un’alternativa
Un’alternativa
RemObjects SDK non
Quando fallisce la variante basata su Accept-Header?
Nella pratica ci sono tre punti critici tipici che affrontare a priori:
- Proxy-Policies: Alcune regole di Reverse Proxy/WAF normalizzano o filtrano l’header Accept. In tal caso la vostra API ricade silenziosamente sulla versione di default. Soluzione: verificare esplicitamente le regole del proxy e, se necessario, ricorrere a
X-Api-Versioncome fallback. - Client-Libraries: Alcune librerie HTTP impostano Accept header 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 in modo tollerante lato server.
Accept (Vary: Accept), altrimenti consegna la Versione 1 a client della Versione 2. Soluzione: impostare consapevolmente Vary, oppure disabilitare il caching a livello di API.Snippet di sorgente: Request-Context, Correlation-ID, Versione e Error-Mapping
Il codice è intenzionalmente strutturato in modo da poter essere integrato in progetti server RemObjects esistenti: un piccolo layer di contesto, un parser per la versione API (da Accept), un meccanismo di Correlation-ID e un mapping centrale delle eccezioni. Termini:
- Correlation-ID: ID univoca per richiesta, che ricompare nella Response e viene riferita nei log.
- Exception-Mapping: Traduzione delle eccezioni interne Delphi in oggetti di errore stabili, elaborabili dal client (incl. HTTP-Status).
- Contract-Version: Versione del contratto JSON che regola comportamento e campi.
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.Scopo: contesto di richiesta stabile anziché „da qualche parte nel Threadlocal“
Il frammento separa intenzionalmente: TApiContext è lo stato minimo che volete propagare. In RemObjects SDK molte funzionalità passano tramite contesto Server/Channel. In progetti eterogenei (p.es. thread worker aggiuntivi, code DB, job in background) il passaggio esplicito è spesso più robusto rispetto a thread-local impliciti, perché rende più visibili concorrenza e cambi di contesto.
Condizioni: 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.
Trappole: La versioning tramite Accept è efficace quanto i vostri test. Se i client utilizzano librerie che sovrascrivono Accept, un’API può improvvisamente ricadere sul valore di default. Per client legacy un fallback di default è sensato, ma deve essere visibile nel monitoring (p.es. avviso di log „Version defaulted“).
Varianti: Se preferite versionare tramite X-Api-Version: il parser è identico, cambia solo la sorgente dell’header. 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 si ottiene quando applicate la meccanica in modo coerente al bordo del server: una volta, all’ingresso della request, leggere dagli header; e una volta, all’uscita delle eccezioni, tradurre in una response stabile. A seconda dell’hosting (p.es. RO-HTTP-Server, IIS-Hosting, self-hosted Windows-/Windows- und Linux-Services) i punti di hook concreti cambiano; il principio resta lo stesso: costruire il contesto, invocare la business logic, mappare centralmente le eccezioni.
Nei progetti RemObjects si lavora spesso direttamente a livello di singola service-method. All’inizio questo scala bene, ma in esercizio fallisce: ogni metodo implementa logging e gestione degli errori in modo diverso. Un punto di separazione pulito è una base di servizio o un dispatcher che standardizzi il comportamento.
Procedura pratica (volutamente breve e vicina all’implementazione)
- Leggere la Correlation-ID dall’header della request
X-Correlation-ID; se manca, generarne una sul server (p.es. GUID). - Leggere la versione del contratto da
Accept(o daX-Api-Version). - Registrare l’inizio della request nel log: metodo, percorso, Correlation-ID, IP remoto, avviare la misurazione della durata.
- Eseguire la business logic; incapsulare gli accessi al DB, dove possibile, in transazioni.
- Catturare le eccezioni: determinare lo status HTTP, creare un oggetto errore JSON, impostare l’header di response
X-Correlation-ID. - Registrare la fine della request nel log: status, durata, eventuale codice di errore.
Threading nel server: perché la Correlation-ID diventa inutile senza disciplina del contesto
Un caso limite Delphi comune: 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à successivamente le righe di log. Se la Correlation-ID è nota solo „all’inizio“, la tracciabilità si perde.
Regola pragmatica: tutto ciò che non rimane strettamente nel thread della request riceve il contesto passato in modo esplicito. Anche se ciò comporta liste di parametri più lunghe, è conveniente nel lungo periodo. In alternativa si può usare un oggetto contesto chiaramente definito, che venga passato intenzionalmente ai worker (anziché variabili globali o singleton nascosti).
Punti critici tipici nei server RemObjects/Delphi:
- Connessioni DB per thread: BDE-Ablosung mit nativer Anbindung-Verbindungen non sono automaticamente condivisibili in modo thread-safe. Un connection pool o una connessione per thread è spesso più sensato di „una global connection“.
- Confini delle transazioni: se all’interno di una richiesta avete più passaggi che appartengono insieme, la transazione deve rimanere nella stessa unità logica. Lavoro asincrono non deve „per errore“ continuare nella stessa transazione.
- Annullamento: se il client interrompe (timeout del proxy, browser chiuso), il server spesso continua a eseguire. Valutate consapevolmente se il lavoro in background abbia ancora senso in quel caso.
Accesso ai dati e codici di errore: 409 non è „anche un 500“
Nei progetti di integrazione una mappatura pulita degli errori è più che cosmetica. Determina se l’interlocutore (ERP-Connector, job ETL, portale clienti) può reagire correttamente. Alcune linee guida pratiche che si sono dimostrate valide negli ambienti Delphi/RemObjects:
- 400 Bad Request: validazione, parametri mancanti/invalidi, JSON non parsabile. Importante: la risposta deve rimanere stabile anche se il body è corrotto.
- 401/403: separare autenticazione e autorizzazione. 401 significa „nessuna/identità non valida“, 403 „identità ok, ma proibito“.
- 404: la risorsa non esiste. Attenzione alla sicurezza: non sempre rivelare se qualcosa esiste.
- 409 Conflict: conflitto funzionale (es. conflitto di versione, „lo stato non permette questa azione“, violazione di chiave univoca se rilevante dal punto di vista funzionale).
- 422 Unprocessable Content: quando sintatticamente è tutto ok ma la validazione funzionale fallisce (non tutti i team usano 422, ma spesso è più chiaro di 400).
- 500: tutto ciò che non riuscite a classificare in modo chiaro. Ci rientrano anche „DB down“, „Timeout“, „Unhandled Exception“.
Trucco specifico per Delphi: molti errori DB emergono come eccezioni generiche. Conviene verificare nello strato di accesso ai dati le situazioni note e trasformarle in EApiError. Importante: non riportare frammenti SQL o nomi interni di tabelle/colonne nel messaggio al client. Questi dettagli devono finire nel log, non nella risposta.
Trucco per il debugging: errori riproducibili tramite „Contract Snapshot“
Insolito, ma estremamente utile in produzione: salvate in caso di errori (o selettivamente per certe Correlation-IDs) uno „snapshot“ composto da header della richiesta + body della richiesta in un file di debug spool. Non è logging permanente (privacy/volume), ma uno strumento controllato per riprodurre casi difficili a partire dall’ambiente di produzione.
Importante: uno snapshot non deve mai persistere in modo non filtrato auth-header, token o dati personali. In pratica significa: Redaction (mascheramento) e attivazione solo tramite feature-flag o whitelist (es. solo per certe Correlation-IDs, finestre temporali brevi).
Implementazione pulita in pratica: mascherare invece di omettere
Nelle integrazioni reali i campi „critici“ sono spesso proprio quelli necessari per il debugging (es. identificatori). Piuttosto che omettere indiscriminatamente, è meglio mascherare: sostituire parzialmente i token, mantenere solo il dominio di una e-mail, conservare solo le ultime cifre dell’IBAN. In questo modo il caso resta riproducibile senza distribuire dati non necessari nel file system. Inoltre lo snapshot dovrebbe essere chiaramente contrassegnato come artefatto di debug e avere un periodo di conservazione definito.
Sicurezza e funzionamento: inoltro degli header, catene di proxy e timeout
Un’API REST raramente termina direttamente sul client. Tipiche sono catene di reverse proxy, terminazione TLS, WAF o API-Gateway. Da questo derivano considerazioni pratiche:
- IP remota: Non affidatevi ciecamente a
X-Forwarded-For. Accettatelo solo da proxy di fiducia e altrimenti usate l’IP diretto del socket. Nei manuali operativi deve essere indicato quali hop sono “trusted”. - Timeout: Se il proxy ha 30 secondi ma il vostro backend richiede 2 minuti, generate Ghost-Requests. Impostate i timeout in modo coerente lungo tutta la catena e decidete: richiesta sincrona oppure job-pattern (202 Accepted + endpoint di stato).
- Correlation-ID: Inserite la Correlation-ID negli header di risposta, in modo che gli amministratori possano correlare i log con il lato client. Se un gateway utilizza proprie Request-IDs: registrate e mappate entrambe le ID.
- Testi di errore: In produzione non esporre dettagli interni. I dettagli di debug solo in modo controllato (stage/feature-flag) e, in caso di dubbio, solo nei log.
Inquadramento: Perché RemObjects SDK può essere vantaggioso qui
Negli ecosistemi Delphi i REST-Server sono spesso implementati con framework leggeri (es. router HTTP minimalisti). RemObjects SDK esprime i suoi punti di forza quando avete già o richiedete un’architettura multi-layer:
- Confini di servizio chiari: i metodi di servizio sono espliciti e i contract sono versionabili.
- Trasporti e serializzazione: potete usare JSON, ma anche altri formati di messaggio (a seconda del setup), senza mescolare la logica di dominio.
- Operatività: opzioni di hosting e integrazione nei servizi esistenti Windows- e Linux-Services sono pianificabili, inclusi roll-out ordinati.
L’approccio mostrato aggiunge le parti che nella pratica spesso mancano: oggetti errore uniformi, versioning deterministico e logging correlabile. Soprattutto per software aziendale su misura con cicli di vita lunghi, questo consente di risparmiare tempo durante gli update e nelle integrazioni con sistemi esterni.
Conclusione: Vale la pena lo sforzo — e dove l’approccio si rivela controproducente?
Il valore emerge quando la vostra interfaccia REST non si limita a “funzionare”, ma è gestibile a lungo termine: contratti JSON stabili, versioning senza proliferazione di URL, errori tracciabili e debugging non basato su tentativi. Proprio in questi ambiti l’approccio con Context, Correlation-ID e mappatura centralizzata delle eccezioni in RemObjects SDK è efficace.
Limiti di impiego: Se avete un singolo endpoint effimero senza partner di integrazione, la Media-Type-Versionierung può rapidamente diventare overengineering. Anche lo snapshot-logging è sensato solo se implementate con disciplina redaction e attivazione. E: se il vostro proxy-stack «ottimizza» o rimuove header, dovete prima riallineare l’infrastruttura, altrimenti debuggerete lo strato sbagliato.
Se dovete modernizzare una landscape di server Delphi esistente o integrare in modo pulito una soluzione software di processo con ERP/DMS/CRM, questi meccanismi spesso fanno la differenza tra “funziona nei test” e “funziona in produzione”.
Nel contesto applicativo rivestono inoltre un ruolo importante Delphi REST-API e REST-Server e Remobjects Sdk Delphi, quando integrazioni, flussi di dati e sviluppo devono interagire correttamente.
Discutere il progetto o un 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 dall'inizio.
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 rimandati a fasi successive.
- Vede in anticipo quale percorso è economicamente ed operativamente sostenibile.