De la tema din revistă la practica în proiecte
Pagini relevante de servicii și pagini tehnice pentru articol
De ce „REST API mit RemObjects SDK” în practică decide adesea la margini
O REST API mit RemObjects SDK nu se joacă de obicei la nivelul unui „Hello World”-service, ci se cântărește acolo unde operarea, legacy-ul și integrarea se întâlnesc: versionare fără oprire, comportament de eroare consistent pe toate endpoint-urile, debugging reproducibil în lanțuri de proxy și capacitatea de a corela în mod unic cererile în caz de problemă.
RemObjects SDK aduce multă infrastructură pentru asta: 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. Ceea ce lipsește însă frecvent în peisajele de software de business dezvoltate în timp este un contract aplicat consecvent: care câmpuri JSON sunt stabile? Cum semnalizăm erorile? Cum recunoaștem o cerere după ce a traversat Load Balancer, terminarea TLS și mai multe straturi de backend?
Următoarea abordare (inclusiv un Delphi-snippet) prezintă o linie robustă pentru RemObjects SDK: versionarea contractelor JSON, impunerea unei Correlation-ID (ID de cerere pentru urmărire), traducerea excepțiilor în coduri de stare HTTP și obiecte JSON de eroare și, în același timp, integrarea debugging-ului cu operarea fără a le contrapune. În plus, analizăm cazuri-limită care apar frecvent în medii reale: concurența/threads pe server, acces la baze de date în contextul unei BDE-Ablösung cu legare nativă, header-e de 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 pe termen lung (de ex. legături ERP/DMS/CRM) conduce adesea la duplicarea URL-urilor, rute dublate, teste dublate și întrebarea „Pe ce versiune lucrăm de fapt?” în documentația de operare.
O alternativă este versionarea prin Media Type (Content Negotiation). Clientul trimite, de exemplu, Accept: application/vnd.company.order+json;v=2. Serverul citește versiunea în mod determinist și ajustează comportamentul contractului/DTO-urilor. Aceasta funcționează în lanțuri de proxy și cache-uri dacă header-ele sunt propagate corect. Pentru administratori este, de asemenea, ușor de verificat: o cerere poate fi reprodusă cu Curl/Postman fără a schimba URL-urile.
RemObjects SDK nu este „REST-puristisch”, ci un framework pragmatic de servicii. Tocmai de aceea merită varianta prin Media Type: puteți păstra endpoint-uri stabile și, în același timp, evolua contractele. Important este să evaluați versiunea întotdeauna, să decideți centralizat și să preluați rezultatul în contextul serviciului.
Când e compromisă varianta cu Accept-Header?
În practică există trei puncte de rupere tipice pe care ar trebui să le abordați dinainte:
- Politici de proxy: Unele Reverse Proxies/reguli WAF normalizează sau filtrează header-ul Accept. Atunci API-ul dvs. revine silențios la valoarea implicită. Soluție: verificați explicit regulile proxy, eventual recurgeți la
X-Api-Versionca fallback. - Biblioteci client: Unele librării HTTP setează propriile Accept-header-e și suprascriu valorile. Soluție: suportați versiunea contractului și ca parametru opțional de query (doar ca fallback), sau parsați Accept-header-ul server-side tolerant.
Accept (Vary: Accept), altfel va livra versiunea 1 către clienții pentru versiunea 2. Soluție: setați în mod explicit Vary sau dezactivați caching-ul la nivel de API.Fragment de cod sursă: Request-Context, Correlation-ID, Version și Error-Mapping
Codul este conceput astfel încât să poată fi integrat în proiecte RemObjects-Server existente: un strat mic de context, un parser pentru versiunea API (din Accept), un mecanism Correlation-ID și un Exception-Mapping centralizat. Termeni:
- Correlation-ID: ID unic pentru fiecare solicitare, care apare din nou în răspuns și este inclus în jurnale.
- Exception-Mapping: Traduce excepții interne Delphi în obiecte de eroare stabile, procesabile de client (inclusiv statutul HTTP).
- Contract-Version: Versiunea contractului JSON care controlează comportamentul și câmpurile.
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 thread-local”
Fragmentul separă intenționat: TApiContext este starea minimă pe care doriți să o transmiteți. În RemObjects SDK multe se desfășoară prin contextul server/canal. În proiecte eterogene (de ex. thread-uri worker suplimentare, coadă DB, joburi de fundal) transmiterea explicită este însă de multe ori mai robustă decât variabilele thread-local implicite, deoarece face concurența și schimbările de context mai vizibile.
Condiții cadru: Varianta cu headerul Accept presupune că reverse proxy-ul dvs. (nginx, IIS ARR, Traefik) transmite headerul neschimbat. În unele medii, headerele Accept „neobișnuite” sunt filtrate sau condensate.
Capcane: Versionarea prin Accept este la fel de bună ca testele dvs. Dacă clienții folosesc librării care suprascriu Accept, o API poate să revină 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 să faceți versionarea prin X-Api-Version: parserul este identic, doar sursa este un alt header. Din perspectiva gateway-urilor asta este uneori mai ușor de controlat.
Integrare în RemObjects SDK: Correlation-ID și maparea excepțiilor la intrarea în serviciu
Efectul real apare dacă aplicați mecanica consecvent la marginea serverului: odată la intrarea requestului citiți din headere, odată la ieșirea excepției traduceți într-un răspuns stabil. În funcție de hosting (de ex. RO-HTTP-Server, IIS-Hosting, serviciu Windows-/Windows- și Linux-Services) punctele concrete de hook diferă; principiul rămâne același: construiți contextul, apelați logica de business, mapați excepțiile centralizat.
În proiectele RemObjects se lucrează frecvent direct la nivel de metodă a serviciului. La început aceasta scalează bine, dar în producție se deteriorează: fiecare metodă implementează logging-ul și tratarea erorilor diferit. O separare curată este o bază de serviciu sau un dispatcher care standardizează.
Flux practic (intenționat scurt și orientat spre implementare)
- Citiți Correlation-ID din headerul request
X-Correlation-ID; dacă lipsește, generați-o server-side (de ex. GUID). - Citiți versiunea contractului din
Accept(sau dinX-Api-Version). - Logați începutul requestului: metodă, cale, Correlation-ID, IP remote, porniți măsurarea duratei.
- Executați logica de business; încapsulați accesul la DB cât mai tranzacțional.
- Prindeți excepțiile: determinați statusul HTTP, generați un obiect JSON de eroare, setați headerul de răspuns
X-Correlation-ID. - Logați sfârșitul requestului: status, durată, eventual cod de eroare.
Threading pe server: de ce Correlation-ID devine inutilă fără disciplină de context
Un caz limită frecvent în Delphi: metoda serviciului declanșează muncă asincronă (de ex. generare de rapoarte, import, push într-un DMS). Atunci thread-ul inițial al requestului 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 requestului primește contextul transmis explicit. Chiar dacă pare că adaugă mai mulți parametri, merită. Alternativ puteți lucra cu un obiect context clar definit, care este transmis în mod deliberat worker-ilor (în loc de variabile globale sau singletons ascunse).
Puncte de cotitură tipice în serverele RemObjects/Delphi:
- DB-Connections pro Thread: BDE-Ablosung mit nativer Anbindung-Verbindungen sind nicht automatisch thread-sicher teilbar. Ein Connection-Pool oder pro Thread eine Verbindung ist häufig sinnvoller als „eine globale Connection“.
- Transaktionsgrenzen: Wenn Sie innerhalb eines Requests mehrere Schritte haben, die zusammengehören, muss die Transaktion in der gleichen logischen Einheit bleiben. Asynchrone Arbeit darf nicht „aus Versehen“ in der gleichen Transaktion weiterlaufen.
- Anulare: Wenn der Client abbricht (Proxy timeout, Browser geschlossen), läuft der Server oft weiter. Überlegen Sie bewusst, ob Hintergrundarbeit dann noch Sinn ergibt.
Datenzugriff und Fehlercodes: 409 ist nicht „auch ein 500“
In Integrationsprojekten ist sauberes Error-Mapping mehr als Kosmetik. Es entscheidet, ob ein Gegenüber (ERP-Connector, ETL-Job, Kundenportal) korrekt reagieren kann. Ein paar praxisnahe Leitplanken, die sich in Delphi/RemObjects-Umgebungen bewährt haben:
- 400 Bad Request: Validierung, fehlende/ungültige Parameter, JSON nicht parsebar. Wichtig: Die Antwort soll stabil bleiben, auch wenn der Body kaputt ist.
- 401/403: Authentifizierung vs. Berechtigung trennen. 401 bedeutet „keine/ungültige Identität“, 403 „Identität ok, aber verboten“.
- 404: Ressource existiert nicht. Vorsicht bei Security: Nicht immer verraten, ob etwas existiert.
- 409 Conflict: Fachlicher Konflikt (z. B. Versionskonflikt, „Status erlaubt diese Aktion nicht“, eindeutige Schlüsselverletzung, wenn sie fachlich relevant ist).
- 422 Unprocessable Content: Wenn syntaktisch alles ok ist, aber fachliche Validierung scheitert (nicht jedes Team nutzt 422, aber es ist oft klarer als 400).
- 500: Alles, was Sie nicht sauber klassifizieren können. Dazu gehört auch „DB down“, „Timeout“, „Unhandled Exception“.
Delphi-spezifischer Kniff: Viele DB-Fehler kommen als generische Exceptions hoch. Es lohnt sich, an der Datenzugriffsschicht gezielt auf bekannte Situationen zu prüfen und sie in EApiError zu überführen. Wichtig dabei: Keine SQL-Fragmente oder internen Tabellen-/Spaltennamen in die Client-Message übernehmen. Diese Details gehören ins Log, nicht in die Response.
Debugging-Kniff: reproduzierbare Fehler durch „Contract Snapshot“
Ungewöhnlich, aber im Betrieb extrem hilfreich: Speichern Sie bei Fehlern (oder gezielt bei bestimmten Correlation-IDs) einen „Snapshot“ aus Request-Headern + Request-Body in einer Debug-Spool-Datei. Das ist kein Dauerlogging (Datenschutz/Volumen), sondern ein kontrolliertes Werkzeug, um schwer reproduzierbare Fälle aus Produktionsnähe nachzustellen.
Wichtig: Ein Snapshot darf niemals ungefiltert Auth-Header, Tokens oder personenbezogene Daten persistieren. In der Praxis bedeutet das: Redaction (Maskierung) und Aktivierung nur über Feature-Flag oder Whitelist (z. B. nur für bestimmte Correlation-IDs, kurze Zeitfenster).
Saubere Umsetzung in der Praxis: Maskieren statt Weglassen
In echten Integrationen sind gerade die „kritischen“ Felder oft die, die man zum Debuggen bräuchte (z. B. Identifikatoren). Statt pauschalem Weglassen ist Maskieren besser: Token teilweise ersetzen, E-Mail nur Domain behalten, IBAN nur die letzten Ziffern. So bleibt der Fall reproduzierbar, ohne unnötige Daten im Dateisystem zu verteilen. Zusätzlich sollte der Snapshot klar als Debug-Artefakt gekennzeichnet sein und eine definierte Aufbewahrungszeit haben.
Sicherheit und Betrieb: Header-Weitergabe, Proxy-Ketten und Timeouts
Eine REST API endet selten direkt am Client. Typisch sind Ketten aus Reverse Proxy, TLS-Termination, WAF oder API-Gateway. Daraus ergeben sich praktische Punkte:
- Remote IP: Verlassen Sie sich nicht blind auf
X-Forwarded-For. Nur aus vertrauenswürdigen Proxies übernehmen und sonst die direkte Socket-IP nutzen. In Betriebshandbüchern sollte stehen, welche Hops „trusted“ sind. - Timeouts: Wenn Proxy 30 Sekunden hat, Ihr Backend aber 2 Minuten braucht, erzeugen Sie Ghost-Requests. Legen Sie Timeouts entlang der Kette konsistent fest und entscheiden Sie: synchroner Request oder Job-Pattern (202 Accepted + Status-Endpunkt).
- Correlation-ID: Setzen Sie die Correlation-ID in Response-Headern, damit Admins sie aus Logs und Client-Seite zusammenführen können. Wenn ein Gateway eigene Request-IDs nutzt: beide IDs loggen und abbilden.
- Fehlertexte: Im Produktivbetrieb keine internen Details. Debug-Details nur kontrolliert (Stage/Feature-Flag) und im Zweifel nur im Log.
Einordnung: Warum RemObjects SDK hier im Vorteil sein kann
In Delphi-Ökosystemen werden REST-Server oft mit leichteren Frameworks (z. B. minimalistische HTTP-Router) gebaut. RemObjects SDK spielt seine Stärke aus, wenn Sie bereits eine mehrschichtige Architektur haben oder brauchen:
- Klare Service-Grenzen: Service-Methoden sind explizit, Contracts sind versionierbar.
- Transporte und Serialisierung: Sie können JSON sprechen, aber auch andere Message-Formate (je nach Setup), ohne die Fachlogik zu verquirlen.
- Betrieb: Hosting-Optionen und Integration in bestehende Windows- und Linux-Services sind planbar, inklusive sauberer Rollouts.
Der gezeigte Ansatz ergänzt das um die Teile, die im Alltag oft fehlen: einheitliche Fehlerobjekte, deterministische Versionierung und korrelierbares Logging. Gerade bei individueller Unternehmenssoftware mit langen Lebenszyklen sparen Sie damit Zeit bei Updates und bei der Integration externer Systeme.
Fazit: Lohnt sich der Aufwand – und wo kippt der Ansatz?
Der Mehrwert entsteht, wenn Ihre REST-Schnittstelle nicht nur „funktioniert“, sondern dauerhaft betreibbar ist: stabile JSON-Verträge, Versionierung ohne URL-Wildwuchs, nachvollziehbare Fehler und Debugging ohne Ratespiel. Genau dort ist der Ansatz mit Context, Correlation-ID und zentralem Exception-Mapping in RemObjects SDK stark.
Einsatzgrenzen: Wenn Sie nur einen einzelnen, kurzlebigen Endpunkt ohne Integrationspartner haben, wirkt Media-Type-Versionierung schnell wie Overengineering. Auch Snapshot-Logging ist nur sinnvoll, wenn Sie Redaction und Aktivierung diszipliniert implementieren. Und: Wenn Ihr Proxy-Stack Header „optimiert“ oder entfernt, müssen Sie zuerst die Infrastruktur geradeziehen, sonst debuggen Sie die falsche Schicht.
Wenn Sie eine bestehende Delphi-Serverlandschaft modernisieren oder eine prozessnahe Softwarelösung sauber in ERP/DMS/CRM integrieren müssen, sind genau diese Mechanismen aber häufig der Unterschied zwischen „läuft im Test“ und „läuft im Betrieb“.
În mediul funcțional, Delphi REST-API și REST-Server și Remobjects Sdk Delphi joacă, de asemenea, un rol important atunci când integrările, fluxurile de date și dezvoltarea ulterioară trebuie să interacționeze coerent.
Discutați proiectul sau demersul de modernizare cu Net-Base.
Următorul pas
Când o temă devine un proiect real, arhitectura, infrastructura existentă și operarea trebuie analizate împreună de la început.
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 Rollout nu sunt amânate ca consecințe ulterioare.
- Veți vedea din timp ce cale este viabilă din punct de vedere economic și operațional.