Du thème du magazine à la pratique des projets
Pages de services et techniques pertinentes pour l'article
Pourquoi une «REST API avec RemObjects SDK» fait souvent la différence sur les cas limites en pratique
Une REST API avec RemObjects SDK ne se mesure pas rarement au service «Hello World», mais aux endroits où exploitation, systèmes hérités et intégration se rencontrent : versionnement sans interruption, comportement d’erreur cohérent sur tous les points de terminaison, débogage reproductible dans des chaînes de proxy et capacité à corréler de manière univoque les requêtes en cas de problème.
RemObjects SDK apporte beaucoup d’infrastructure : services, formats de messages, sérialisation, hébergement (par ex. en tant que Windows- et Linux-Services ou derrière IIS/Reverse Proxy) et des points définis pour traiter les erreurs de façon centralisée. Ce qui manque toutefois souvent dans des environnements de logiciels métier évolués, c’est un contrat appliqué de façon cohérente : quels champs JSON sont stables ? Comment signalons‑nous les erreurs ? Comment reconnaître une requête après qu’elle a traversé load balancers, terminaison TLS et plusieurs couches backend ?
L’approche suivante (incluant des snippets Delphi) propose une ligne robuste pour RemObjects SDK : versionner les contrats JSON, imposer une Correlation-ID (Request‑ID pour le suivi), traduire les Exceptions en statuts HTTP et en objets d’erreur JSON tout en veillant à ne pas opposer débogage et exploitation. Nous examinons en outre des cas limites rencontrés régulièrement en production : gestion des threads côté serveur, accès base de données lors de la remplacement BDE par une liaison native, en‑têtes proxy, timeouts et payloads clients « sales ».
Décision d’architecture : versionnement via le type média plutôt que l’URL
Beaucoup d’APIs versionnent via des chemins tels que /v1/. C’est pragmatique, mais sur des intégrations de longue durée (par ex. liaisons ERP/DMS/CRM) cela conduit souvent à des duplications d’URL, des routes en double, des tests en double et à la question «Quelle version utilisons‑nous réellement ?» dans les guides d’exploitation.
Une alternative est le versionnement via le Media Type (content negotiation). Le client envoie par exemple Accept: application/vnd.company.order+json;v=2. Le serveur extrait la version de manière déterministe et adapte le comportement du contrat/DTO. Cela fonctionne dans des chaînes de proxy et de cache si les en‑têtes sont correctement propagés. Pour les administrateurs, c’est en outre vérifiable : une requête peut être reproduite avec curl/Postman sans que les URLs diffèrent.
RemObjects SDK n’est pas «REST‑puriste», mais un framework de services pragmatique. C’est précisément pour cela que la variante par type média est intéressante : vous pouvez conserver des endpoints stables tout en faisant évoluer les contrats. L’important est d’analyser la version toujours, de décider de façon centralisée en un seul point et de propager le résultat dans votre contexte de service.
Quand la variante basée sur l’en‑tête Accept montre‑t‑elle ses limites ?
En pratique, il existe trois points de rupture typiques à traiter en amont :
- Politiques de proxy : Certaines règles de reverse proxy/WAF normalisent ou filtrent l’en‑tête Accept. Votre API retombe alors silencieusement sur la valeur par défaut. Solution : vérifier explicitement les règles du proxy, et le cas échéant basculer sur
X-Api-Version. - Bibliothèques clientes : Certains clients HTTP définissent leurs propres en‑têtes Accept et écrasent les valeurs. Solution : supporter la version du contrat également comme paramètre de requête optionnel (uniquement en fallback), ou analyser l’en‑tête Accept côté serveur de façon tolérante.
Accept (Vary: Accept), sinon il renverra la version 1 aux clients de la version 2. Solution : définir explicitement Vary ou désactiver la mise en cache au niveau de l’API.Extrait de code source : Contexte de requête, Correlation-ID, version et mappage d’erreurs
Le code est volontairement conçu pour pouvoir être intégré dans des projets serveur RemObjects existants : une petite couche de contexte, un parseur pour la version de l’API (à partir de Accept), un mécanisme de Correlation-ID et un mappage central des exceptions. Définitions :
- Correlation-ID : ID unique par requête, renvoyée dans la réponse et référencée dans les logs.
- Exception-Mapping : traduction des exceptions internes Delphi en objets d’erreur stables, exploitables côté client (incl. statut HTTP).
- Contract-Version : version du contrat JSON qui contrôle le comportement et les champs.
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.Objectif : contexte de requête stable plutôt que « quelque part dans le threadlocal »
Le snippet sépare délibérément : TApiContext est l’état minimal que vous souhaitez transmettre. Dans RemObjects SDK, beaucoup de choses passent via le contexte Serveur/Channel. Dans des projets hétérogènes (p. ex. threads workers supplémentaires, DB-Queue, tâches en arrière-plan), le passage explicite est souvent plus robuste que les threadlocals implicites, car il rend la concurrence et les changements de contexte plus visibles.
Conditions préalables : La variante via l’Accept-Header suppose que votre reverse proxy (nginx, IIS ARR, Traefik) relaie l’en-tête sans modification. Dans certains environnements, des Accept-Header « inhabituels » sont filtrés ou regroupés.
Pièges : Le versionnement via Accept n’est fiable que dans la mesure où le sont vos tests. Si des bibliothèques clientes réécrivent Accept, une API peut soudain retomber sur la valeur par défaut. Pour des clients legacy, un fallback par défaut est judicieux, mais il doit être visible dans le monitoring (p. ex. alerte de log « Version defaulted »).
Variantes : Si vous préférez faire le versionnement via X-Api-Version : le parser est identique, seule la source est un autre header. Du point de vue des passerelles, c’est parfois plus simple à contrôler.
Intégration dans RemObjects SDK : Correlation-ID et mappage d’exceptions à l’entrée du service
L’effet réel apparaît lorsque vous appliquez la mécanique de manière cohérente à la périphérie de votre serveur : lire une fois depuis les headers à l’entrée de la requête, traduire une fois à la sortie des exceptions en une réponse stable. Selon l’hébergement (p. ex. RO-HTTP-Server, IIS-Hosting, Windows auto-hébergé-/Windows- et Linux-services), les points d’accroche concrets diffèrent ; le principe reste le même : construire le contexte, appeler la logique métier, mapper les exceptions de manière centralisée.
Dans les projets RemObjects, on travaille souvent directement au niveau de chaque méthode de service. Cela évolue bien au début, mais se dégrade en exploitation : chaque méthode met en place le logging et la gestion d’erreurs différemment. Une séparation propre est une base de service ou un dispatcher standardisé.
Processus pratique (volontairement court et orienté implémentation)
- Lire la Correlation-ID depuis le header de la requête
X-Correlation-ID; si elle est absente, la générer côté serveur (p. ex. GUID). - Lire la version du contrat depuis
Accept(ou depuisX-Api-Version). - Consigner le début de la requête : méthode, chemin, Correlation-ID, IP distante, démarrer la mesure de durée.
- Exécuter la logique métier ; encapsuler les accès DB autant que possible dans des transactions.
- Intercepter les exceptions : déterminer le statut HTTP, générer un objet d’erreur JSON, définir le header de réponse
X-Correlation-ID. - Consigner la fin de la requête : statut, durée, le cas échéant code d’erreur.
Threading côté serveur : pourquoi la Correlation-ID devient inutile sans discipline de contexte
Un cas limite fréquent Delphi : la méthode de service déclenche du travail asynchrone (p. ex. génération de rapports, import, push vers un DMS). Le thread de requête initial n’est alors plus celui qui écrira les lignes de log ultérieurement. Si la Correlation-ID n’est connue qu’« au départ », la traçabilité se dissout.
Règle pragmatique : tout ce qui ne reste pas strictement dans le thread de la requête reçoit le contexte transmis explicitement. Même si cela alourdit les listes de paramètres, cela en vaut la peine. En alternative, on peut utiliser un objet de contexte clairement défini, qui est consciemment transmis aux workers (plutôt que des variables globales ou des singletons cachés).
Points de bascule typiques dans les serveurs RemObjects/Delphi :
- Connexions DB par thread : BDE-Ablosung mit nativer Anbindung-connexions ne sont pas automatiquement partageables de manière sûre entre threads. Un pool de connexions ou une connexion par thread est souvent plus judicieux qu’une « connexion globale ».
- Limites de transaction : Si, dans une requête, vous avez plusieurs étapes qui font partie d’une même unité logique, la transaction doit rester dans cette même unité. Un travail asynchrone ne doit pas, « par accident », se poursuivre dans la même transaction.
- Annulation : Si le client abandonne (timeout du proxy, fermeture du navigateur), le serveur continue souvent à s’exécuter. Réfléchissez consciemment pour savoir si le travail en arrière-plan a encore du sens dans ce cas.
Accès aux données et codes d’erreur : 409 n’est pas « aussi un 500 »
Dans les projets d’intégration, un mapping d’erreurs propre est plus que cosmétique. Il détermine si un interlocuteur (ERP-Connector, ETL-Job, portail client) peut réagir correctement. Quelques garde-fous pratiques qui se sont avérés dans les environnements Delphi/RemObjects :
- 400 Bad Request : Validation, paramètres manquants/invalide, JSON non analysable. Important : la réponse doit rester stable, même si le corps est corrompu.
- 401/403 : Séparer authentification et autorisation. 401 signifie « identité absente/invalide », 403 « identité ok, mais interdite ».
- 404 : Ressource inexistante. Attention en matière de sécurité : ne pas toujours révéler l’existence d’une ressource.
- 409 Conflict : Conflit métier (p. ex. conflit de version, « le statut n’autorise pas cette action », violation de contrainte d’unicité lorsque cela a une portée métier).
- 422 Unprocessable Content : Lorsque la syntaxe est correcte mais que la validation métier échoue (toutes les équipes n’utilisent pas 422, mais c’est souvent plus clair que 400).
- 500 : Tout ce que vous ne pouvez pas classer proprement. Cela inclut aussi « DB down », « Timeout », « Unhandled Exception ».
Astuce spécifique à Delphi : de nombreuses erreurs de BD remontent sous forme d’exceptions génériques. Il vaut la peine, au niveau de la couche d’accès aux données, de détecter spécifiquement les situations connues et de les convertir en EApiError. Important : ne pas reprendre des fragments SQL ni des noms internes de tables/colonnes dans le message client. Ces détails appartiennent au log, pas à la réponse.
Astuce de debugging : erreurs reproductibles grâce au « Contract Snapshot »
Inhabituel, mais extrêmement utile en production : en cas d’erreur (ou ciblé pour certaines Correlation-IDs), enregistrez un « Snapshot » constitué des en-têtes de requête + du corps de requête dans un fichier de spool de debug. Ce n’est pas un logging continu (protection des données/volume), mais un outil contrôlé pour rejouer des cas difficiles à reproduire à partir du terrain.
Important : un snapshot ne doit jamais persister des Auth-Header, tokens ou données personnelles sans filtrage. En pratique, cela signifie : redaction (masquage) et activation uniquement via feature-flag ou liste blanche (p. ex. seulement pour certaines Correlation-IDs, fenêtres temporelles courtes).
Implémentation propre en pratique : masquer plutôt que supprimer
Dans de véritables intégrations, ce sont souvent les champs « critiques » qui sont nécessaires pour le debugging (p. ex. les identifiants). Plutôt que de supprimer systématiquement, il vaut mieux masquer : remplacer partiellement les tokens, ne conserver que le domaine d’un e‑mail, ne garder que les derniers chiffres de l’IBAN. Ainsi le cas reste reproductible sans répandre des données inutiles dans le système de fichiers. De plus, le snapshot doit être clairement identifié comme artefact de debug et avoir une durée de conservation définie.
Sécurité et exploitation : transmission des en-têtes, chaînes de proxy et timeouts
Une API REST ne se termine que rarement directement côté client. On trouve typiquement des chaînes composées de Reverse Proxy, de terminaison TLS, de WAF ou d’API-Gateway. Cela donne lieu aux points pratiques suivants :
- IP distante : Ne vous fiez pas aveuglément à
X-Forwarded-For. Ne l’acceptez que depuis des proxies de confiance et, sinon, utilisez l’IP directe du socket. Les manuels d’exploitation doivent préciser quels sauts sont considérés comme de confiance. - Timeouts : Si le proxy a 30 secondes, mais que votre backend prend 2 minutes, vous générez des requêtes fantômes. Alignez les timeouts le long de la chaîne de manière cohérente et décidez : requête synchrone ou modèle Job (202 Accepted + endpoint d’état).
- ID de corrélation : Placez l’ID de corrélation dans les en-têtes de réponse afin que les administrateurs puissent la rattacher aux logs et côté client. Si un gateway utilise ses propres Request-IDs : consignez et corrélez les deux IDs.
- Messages d’erreur : En production, pas de détails internes. Les détails de debug uniquement de façon contrôlée (stage / feature-flag) et, à défaut, uniquement dans les logs.
Positionnement : pourquoi RemObjects SDK peut être avantageux ici
Dans les écosystèmes Delphi, les serveurs REST sont souvent construits avec des frameworks plus légers (par ex. des routeurs HTTP minimalistes). RemObjects SDK révèle sa force lorsque vous avez déjà, ou avez besoin d’une architecture en couches :
- Frontières de service claires : les méthodes de service sont explicites, les contracts peuvent être versionnés.
- Transports et sérialisation : vous pouvez utiliser JSON, mais aussi d’autres formats de message (selon le setup), sans mélanger la logique métier.
- Exploitation : les options d’hébergement et l’intégration dans des Windows- et Linux-services existants sont planifiables, y compris des rollouts propres.
L’approche présentée complète cela par des éléments souvent absents au quotidien : objets d’erreur uniformes, versionnage déterministe et logging corrélable. Pour des logiciels d’entreprise sur mesure avec de longs cycles de vie, cela vous fait gagner du temps lors des mises à jour et de l’intégration de systèmes externes.
Conclusion : l’effort en vaut-il la peine — et où la méthode bascule-t-elle ?
La valeur ajoutée apparaît lorsque votre interface REST ne se contente pas de « fonctionner », mais est exploitable durablement : contrats JSON stables, versionnage sans prolifération d’URL, erreurs traçables et débogage sans conjectures. C’est précisément là que l’approche basée sur le contexte, l’ID de corrélation et le mappage centralisé des exceptions dans RemObjects SDK est performante.
Limites d’utilisation : Si vous n’avez qu’un point de terminaison unique et éphémère sans partenaires d’intégration, la Media-Type-Versionierung peut rapidement sembler du sur-ingénierie. La journalisation par snapshot n’a de sens que si vous implémentez de manière disciplinaire le masquage des données (redaction) et l’activation. Et : si votre pile de proxies « optimise » ou supprime des en-têtes, vous devez d’abord rectifier l’infrastructure, sinon vous déboguerez la mauvaise couche.
Si vous modernisez un parc de serveurs Delphi existant ou devez intégrer proprement une solution logicielle proche du processus dans un ERP/DMS/CRM, ces mécanismes font souvent la différence entre « fonctionne en test » et « fonctionne en exploitation ».
Dans l’environnement métier, Delphi REST-API et REST-Server et Remobjects Sdk Delphi jouent également un rôle important lorsque les intégrations, les flux de données et la poursuite du développement doivent s’articuler de manière maîtrisée.
Discuter d’un projet ou d’une opération de modernisation avec Net-Base.
Étape suivante
Lorsque le sujet devient un projet réel, l'architecture, l'existant et l'exploitation doivent être examinés ensemble dès le départ.
Nous n'intervenons pas seulement sur des questions ponctuelles, mais aussi lorsque des fragments de code source, des problématiques liées aux systèmes legacy ou des concepts de portail doivent se transformer en un projet d'entreprise robuste.
- L'état des lieux, l'état cible et les risques techniques sont évalués conjointement.
- REST, l’accès aux données, les portails et le déploiement ne sont pas reportés à des phases ultérieures.
- Vous identifiez tôt quelle voie est viable économiquement et opérationnellement.