Del tema de la revista a la pràctica del projecte
Pàgines de serveis i tècniques pertinents per a l'article
Per què „REST API amb RemObjects SDK“ sovint es decideix en els detalls operatius
Una REST API amb RemObjects SDK rarament es guanya o es perd pel servei „Hello World“, sinó pels punts on l’operació, els sistemes heredats i la integració xoquen: versionament sense aturades, comportament d’errors consistent en tots els punts finals, depuració reproducible en cadenes de proxy i la capacitat de correlacionar de manera inequívoca les peticions en cas de problema.
RemObjects SDK aporta molta infraestructura per a això: serveis, formats de missatge, serialització, hosting (p. ex. com a Windows- i Linux-Services o darrere d’IIS/Reverse Proxy) i punts definits per tractar errors de manera centralitzada. El que sovint manca en paisatges de software empresarial consolidats, però, és un contracte aplicat de manera consistent: quins camps JSON són estables? Com indiquem errors? Com identifiquem una petició després que hagi passat per balancejadors de càrrega, terminació TLS i diverses capes de backend?
L’enfocament següent (inclòs el Delphi-fragment) mostra una línia robusta per a RemObjects SDK: versionar els contractes JSON, Correlation-ID (ID de petició per al seguiment) imposar, traduir Excepcions a l’estat HTTP i a objectes d’error JSON i, alhora, no oposar el debugging i l’operació. A més, abordem casos límit que apareixen de manera recurrent en entorns reals: gestió de fils (threading) al servidor, accessos a base de dades amb la substitució de BDE i connexió nativa, capçaleres de proxy, timeouts i payloads de client „bruts“.
Decisió d’arquitectura: versionament per tipus de mitjà en lloc d’URL
Moltes APIs versionen per rutes com /v1/. Això és pragmàtic, però en integracions de llarg termini (p. ex. connexions ERP/DMS/CRM) sovint comporta duplicació d’URL, rutes duplicades, proves duplicades i la pregunta «Quina versió fem servir realment?» als manuals d’operació.
Una alternativa és el versionament mitjançant el Media Type (Content Negotiation). El client envia, per exemple, Accept: application/vnd.company.order+json;v=2. El servidor llegeix la versió de manera determinista i adapta el comportament del contracte/DTO. Això funciona en cadenes de proxy i caché si els headers es reenviïn correctament. Per als admins també és fàcil de verificar: una petició es pot reproduir amb Curl/Postman sense que les URLs siguin diferents.
RemObjects SDK no és „REST-purista“, sinó un framework de serveis pragmàtic. Precisament per això val la pena la variant per tipus de mitjà: podeu mantenir punts finals estables i, al mateix temps, evolucionar els contractes. És important que valoreu la versió sempre, que decidiu de manera centralitzada i que incorporeu el resultat al context del vostre servei.
Quan falla la variant d’Accept-Header?
En la pràctica hi ha tres punts de ruptura típics que cal abordar prèviament:
- Proxy-Policies: Algunes regles de Reverse Proxy/WAF normalitzen o filtren l’Accept-Header. Llavors la vostra API torna silenciosament al valor per defecte. Solució: comprovar explícitament les regles del proxy; si cal, recórrer a
X-Api-Version. - Client-Libraries: Alguns clients HTTP estableixen els seus propis Accept-Header i sobreescriuen els valors. Solució: admetre la versió del contracte també com a paràmetre de consulta opcional (només com a fallback), o analitzar l’Accept-Header amb tolerància al costat del servidor.
Accept (Vary: Accept), si no lliurarà la versió 1 a clients de la versió 2. Solució: establir conscientment Vary, o desactivar el caching a nivell d’API.Fragment de codi: context de la petició, Correlation-ID, versió i mapeig d’errors
El codi està intencionadament dissenyat per integrar-se en projectes de servidor RemObjects existents: una petita capa de context, un parser per a la versió de l’API (a partir de Accept), un mecanisme de Correlation-ID i un mapeig central d’excepcions. Termes:
- Correlation-ID: ID única per petició, que torna a aparèixer a la resposta i es refereix als registres.
- Exception-Mapping: Traducció d’excepcions internes Delphi a objectes d’error estables i processables pel client (incl. l’estat HTTP).
- Contract-Version: Versió del contracte JSON que controla el comportament i els camps.
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.Propòsit: Context de request estable en comptes de „en algun lloc del Threadlocal“
El snippet separa deliberadament: TApiContext és l’estat mínim que voleu transmetre. En RemObjects SDK moltes coses passen pel context de servidor/canal. En projectes heterogenis (p. ex. threads addicionals de worker, DB-queue, treballs en segon pla) transmetre el context de manera explícita sovint és més robust que dependre de threadlocals implícits, perquè fa més visible la concurrència i els canvis de context.
Condicions marc:** La variant amb l’Accept-Header pressuposa que el vostre reverse proxy (nginx, IIS ARR, Traefik) reenvia l’header sense modificar-lo. En alguns entorns es poden filtrar o agregar els Accept-Header «no habituals».
Pebres on ensopegar: La versionització mitjançant Accept només és tan bona com les vostres proves. Si els clients utilitzen llibreries que sobreescriuen Accept, una API pot caure sobtadament al valor per defecte. Per a clients legacy és raonable tenir un fallback per defecte, però aquest ha de ser visible en el monitoring (p. ex. avis al log «Version defaulted»).
Variants: Si preferiu versionar via X-Api-Version: el parser és idèntic, només canvia la font (un altre header). Des del punt de vista dels gateways això, de vegades, és més senzill de controlar.
Integració en RemObjects SDK: Correlation-ID i Exception-Mapping a l’entrada del servei
L’efecte real es produeix quan apliqueu la mecànica de manera consistent a la vora del vostre servidor: llegir una vegada a l’entrada de la request des dels headers, i una vegada a la sortida d’excepcions traduir a una resposta estable. Segons el hosting (p. ex. RO-HTTP-Server, IIS-Hosting, servei auto-gestionat Windows-/ Windows- i Linux-Services) varien els punts de hook concrets; el principi és el mateix: construir el context, invocar la lògica de negoci, mapar les excepcions de manera centralitzada.
En projectes RemObjects sovint es treballa mètode a mètode per servei. Això escala bé inicialment, però falla en producció: cada mètode construeix logging i gestió d’errors de manera diferent. Un tall net és una base de servei o un dispatcher que estandarditzi aquests passos.
Procediment pràctic (deliberadament curt i proper a la implementació)
- Llegir la Correlation-ID de l’header de la request
X-Correlation-ID; si manca, generar-la al servidor (p. ex. GUID). - Llegir la Contract-Version des de
Accept(o des deX-Api-Version). - Registrar l’inici de la request: mètode, ruta, Correlation-ID, IP remota; iniciar la mesura de durada.
- Executar la lògica de negoci; encapsular els accessos a la DB preferiblement de forma transaccional.
- Capturar excepcions: determinar l’estatus HTTP, generar un objecte d’error JSON, establir el Response-Header
X-Correlation-ID. - Registrar el final de la request: estatus, durada, si escau codi d’error.
Threading al servidor: Per què la Correlation-ID sense disciplina de context deixa de tenir valor
Un cas límit freqüent en Delphi: la mètode del servei dispara treball asíncron (p. ex. generació d’informes, importacions, push a un DMS). Llavors el thread original de la request ja no és el que escriu línies al log més endavant. Si la Correlation-ID només es coneix «al principi», la traçabilitat es desfà.
Regla pragmàtica: tot allò que no romangui estrictament al thread de la request ha de rebre el context passat explícitament. Tot i que sembli afegir més llistes de paràmetres, compensa. Alternativament, treballar amb un objecte de context clarament definit que es passi als workers (en lloc de variables globals o singletons ocults).
Punts clau típics en servidors RemObjects-/Delphi:
- Connexions DB per fil: BDE-Ablosung mit nativer Anbindung-connexions no es poden compartir automàticament de manera segura entre fils. Un Connection-Pool o una connexió per fil és sovint més adient que „una connexió global“.
- Límits de transacció: Si dins d’un Request teniu diversos passos que formen part d’una mateixa unitat, la transacció ha de romandre dins de la mateixa unitat lògica. El treball asíncron no ha de continuar „per accident“ dins de la mateixa transacció.
- Cancel·lació: Si el client aborta (Proxy timeout, Browser closed), sovint el servidor continua executant. Valoreu de manera conscient si el treball en segon pla encara té sentit en aquests casos.
Accés a dades i codis d’error: 409 no és „també un 500“
En projectes d’integració, un mapeig d’errors net és més que cosmètica. Determina si una contraparte (ERP-Connector, ETL-Job, Portal de clients) pot reaccionar correctament. Algunes directrius pràctiques que han demostrat la seva validesa en entorns Delphi/RemObjects:
- 400 Bad Request: Validació, paràmetres faltants/invàlids, JSON no analitzable. Important: la resposta ha de mantenir-se estable, encara que el cos (body) estigui malmès.
- 401/403: Separeu autenticació i autorització. 401 significa „sense/identitat invàlida“, 403 „identitat correcta, però prohibit“.
- 404: Recurs no existeix. Precaució amb la seguretat: no sempre convé revelar si alguna cosa existeix.
- 409 Conflict: Conflicte funcional (p. ex. conflicte de versions, „l’estat no permet aquesta acció“, violació d’una clau única quan sigui rellevant des del punt de vista funcional).
- 422 Unprocessable Content: Quan sintàcticament tot està bé però la validació funcional falla (no tots els equips utilitzen el 422, però sovint és més clar que un 400).
- 500: Tot allò que no pugueu classificar de manera clara. Això inclou també „DB down“, „Timeout“, „Unhandled Exception“.
Delphi-específic: Molts errors de DB es presenten com a excepcions genèriques. Val la pena comprovar a la capa d’accés a dades situacions conegudes i convertir-les a EApiError. Important: no incloure fragments SQL ni noms interns de taules/columnes en el missatge al client. Aquests detalls pertanyen als logs, no a la resposta.
Truc de depuració: errors reproduïbles mitjançant „Contract Snapshot“
Inusual, però extremadament útil en producció: deseu, en cas d’errors (o de forma selectiva per a determinades Correlation-IDs), un „snapshot“ d‘encapçalaments de la sol·licitud + cos de la sol·licitud en un fitxer spool de depuració. Això no és un registre permanent (protecció de dades/volum), sinó una eina controlada per reproduir casos difícils de reproduir a prop de la producció.
Important: Una instantània mai no ha de persistir sense filtrar Auth-Header, Tokens o dades personals. A la pràctica això significa: Redaction (mascarament) i activació només mitjançant Feature-Flag o Whitelist (p. ex. només per a determinades Correlation-IDs, períodes breus).
Implementació neta en la pràctica: mascarar en lloc d’ometre
En integracions reals, sovint els camps „crítics“ són precisament els que necessiteu per depurar (p. ex. identificadors). En lloc d’ometre’ls de manera general, és millor mascarar-los: substituir parcialment tokens, conservar només el domini d’un correu electrònic, mostrar només les darreres xifres de l’IBAN. Així el cas continua sent reproduïble sense dispersar dades innecessàries al sistema de fitxers. A més, la instantània ha d’estar clarament marcada com a artefacte de depuració i tenir un període de conservació definit.
Seguretat i operació: transmissió d’encapçalaments, cadenes de proxy i timeouts
Una REST API rarament acaba directament al client. Són típiques cadenes de reverse proxy, terminació TLS, WAF o API-Gateway. D’això se’n deriven punts pràctics:
- IP remota: No es fiï cegament de
X-Forwarded-For. Només acceptar-la des de proxies de confiança i, en cas contrari, utilitzar l’IP directa del socket. Als manuals d’operació ha d’especificar-se quins salts («hops») són de confiança. - Timeouts: Si el proxy té 30 segons però el vostre backend en necessita 2 minuts, generareu peticions fantasma. Establiu timeouts de manera consistent al llarg de la cadena i decidiu: petició síncrona o patró de treball (202 Accepted + punt d’estat).
- Correlation-ID: Incloeu la Correlation-ID en els headers de resposta perquè els administradors la puguin correlacionar amb els logs i amb el costat client. Si un gateway utilitza les seves pròpies Request-IDs: registreu i mapegeu ambdues IDs.
- Textos d’error: En producció no exposar detalls interns. Els detalls de depuració només de forma controlada (Stage/Feature-Flag) i, si cal, només al log.
Enquadrament: Per què RemObjects SDK pot tenir un avantatge aquí
En els ecosistemes Delphi els REST-Server sovint es construeixen amb marcs més lleugers (p. ex., routers HTTP minimalistes). RemObjects SDK desplega la seva fortalesa quan ja disposeu d’una arquitectura en capes o en necessiteu una:
- Límits clars de servei: els mètodes de servei són explícits i els contracts són versionables.
- Transport i serialització: Podeu utilitzar JSON, però també altres formats de missatge (segons la configuració), sense diluir la lògica de negoci.
- Operació: Les opcions d’allotjament i la integració en serveis existents Windows- i Linux-Services són planificables, incloent desplegaments ordenats.
L’enfocament mostrat completa això amb les parts que sovint falten en el dia a dia: objectes d’error uniformes, versionat determinista i logging correlacionable. Especialment en el cas de software empresarial a mida amb cicles de vida llargs, això us estalvia temps en actualitzacions i en la integració de sistemes externs.
Conclusió: Val la pena l’esforç — i on falla l’enfocament?
El valor afegit apareix quan la vostra interfície REST no només «funciona», sinó que és operable de forma sostenible: contractes JSON estables, versionat sense proliferació d’URL, errors rastrejables i depuració sense endevinalles. Precisament en aquests aspectes l’enfocament amb Context, Correlation-ID i mapeig central d’excepcions en RemObjects SDK és sòlid.
Límits d’ús: Si només teniu un únic endpoint efímer sense partners d’integració, el versionament per Media-Type pot semblar ràpidament un sobredisseny. També el snapshot-logging només té sentit si implementeu disciplinadament la redacció i l’activació. I: si la vostra pila de proxies «optimitza» o elimina headers, primer heu d’alinear la infraestructura; si no, estareu depurant la capa equivocada.
Si heu de modernitzar una arquitectura de servidors Delphi existent o integrar de manera neta una solució de software pròxima al procés en ERP/DMS/CRM, sovint aquests mecanismes són la diferència entre «funciona a les proves» i «funciona en producció».
En l’àmbit funcional també tenen un paper important Delphi REST-API i REST-Server i Remobjects Sdk Delphi quan les integracions, els fluxos de dades i el desenvolupament continu han de funcionar conjuntament de manera ordenada.
Pas següent
Quan d'un tema se'ndevé un projecte real, s'han de considerar aviat i de manera conjunta l'arquitectura, els actius existents i l'operació.
No només donem suport en qüestions puntuals, sinó també quan, a partir de fragments de codi font, temes de sistemes heredats o idees de portal, ha de sorgir un projecte empresarial sòlid.
- L'estat actual, la visió objectiu i els riscos tècnics s'avaluen conjuntament.
- REST, accés a dades, portals i desplegament no es posposaran com a efectes retardats.
- Veu aviat quin camí és viable des del punt de vista econòmic i operatiu.