Du thème du magazine à la pratique des projets
Pages de services et techniques pertinentes pour l'article
Pourquoi «REST API avec RemObjects SDK» fait souvent la différence en pratique
Une REST API avec RemObjects SDK ne se joue rarement sur un service «Hello World», mais sur les endroits où exploitation, legacy et intégration se rencontrent : versionnement sans interruption, comportement d’erreur cohérent sur tous les endpoints, 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 message, sérialisation, hosting (p. 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 souvent dans des environnements logiciels métiers établis, c’est un contrat appliqué de manière cohérente : quels champs JSON sont stables ? Comment signalons-nous les erreurs ? Comment reconnaître une requête une fois qu’elle a traversé un load balancer, une terminaison TLS et plusieurs couches backend ?
L’approche suivante (incluant des snippets Delphi) montre une ligne robuste pour RemObjects SDK : versionner les contrats JSON, imposer la Correlation-ID (Request-ID pour le suivi), traduire les exceptions en statut HTTP et en objets d’erreur JSON tout en évitant d’opposer débogage et exploitation. Nous examinons également les cas limites qui surviennent régulièrement en production : gestion des threads côté serveur, accès base de données avec BDE-remplacement avec liaison native, en-têtes de proxy, timeouts et payloads clients « sales ».
Décision d’architecture : versionnement via le Media Type plutôt que par URL
Beaucoup d’API font du versionnement via des chemins comme /v1/. C’est pragmatique, mais dans des intégrations de longue durée (p. ex. raccordements ERP/DMS/CRM) cela conduit souvent à une duplication d’URLs, des routes en double, des tests en double et la question « quelle version utilise-t-on ? » dans les manuels d’exploitation.
Une alternative est le versionnement via le media type (négociation de contenu). Le client envoie p. ex. Accept: application/vnd.company.order+json;v=2. Le serveur lit 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 aussi vérifiable : une requête peut être reproduite avec curl/Postman sans que les URLs diffèrent.
RemObjects SDK n’est pas un «REST-puristisch», mais un framework de services pragmatique. C’est précisément pourquoi la variante media type est intéressante : vous pouvez conserver des endpoints stables tout en faisant évoluer les contrats. L’essentiel est d’évaluer la version toujours, de décider de façon centralisée en un seul endroit et d’intégrer le résultat dans votre contexte de service.
Quand la variante Accept-Header montre-t-elle ses limites ?
En pratique, il existe trois points de rupture typiques qu’il convient d’adresser en amont :
- Proxy-Policies : certaines règles de reverse proxy/WAF normalisent ou filtrent l’Accept-Header. Votre API retombera 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. - Client-Libraries : certains clients HTTP posent leurs propres Accept-Header et écrasent les valeurs. Solution : supporter la version du contrat également comme paramètre de requête optionnel (uniquement en fallback), ou parser côté serveur l’Accept-Header de façon tolérante.
Accept (Vary: Accept), sinon il renverra la version 1 aux clients de la version 2. Solution : définir consciemment Vary, ou désactiver la mise en cache au niveau de l’API.Extrait de code source : Request-Context, Correlation-ID, Version et Error-Mapping
Le code est volontairement conçu pour pouvoir s’intégrer dans des projets serveurs RemObjects existants : une petite couche de contexte, un parseur pour la version de l’API (depuis Accept), un mécanisme de Correlation-ID et un mappage central des exceptions. Notions :
- 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, traitables par le 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;
// Exemple attendu : 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;
// En production, pas de détails internes, pas de SQL, pas de chemins.
// Pour Debug/Stage, on peut l'étendre via la configuration.
begin
if E is EApiError then
Exit(E.Message);
if E is EArgumentException then
Exit('Paramètres invalides.');
Exit('Erreur interne.');
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 : un contexte de requête stable au lieu de « quelque part dans le Threadlocal »
Le snippet sépare volontairement : TApiContext est l’état minimal que vous souhaitez transmettre. Dans RemObjects SDK, beaucoup repose sur le contexte serveur/channel. Dans des projets hétérogènes (p. ex. threads worker supplémentaires, DB-Queue, jobs en arrière-plan), le passage explicite du contexte est souvent plus robuste que des Threadlocals implicites, car il rend la concurrence et les changements de contexte plus visibles.
Conditions : La variante utilisant l’Accept-Header suppose que votre reverse proxy (nginx, IIS ARR, Traefik) relaie l’en-tête sans le modifier. Dans certains environnements, des Accept-Header « atypiques » sont filtrés ou regroupés.
Pièges : La version via Accept n’est aussi fiable que vos tests. Si des clients utilisent des bibliothèques qui réécrivent Accept, une API peut retomber soudainement sur la valeur par défaut. Pour les clients legacy, un fallback par défaut est pertinent, mais il doit être visible dans le monitoring (p. ex. avertissement de log « Version defaulted »).
Variantes : Si vous préférez versionner via X-Api-Version : le parser est identique, seule la source est un autre en-tête. Du point de vue des gateways, c’est parfois plus simple à contrôler.
Intégration dans RemObjects SDK : Correlation-ID et mapping d’exceptions à l’entrée du service
L’effet réel apparaît lorsque vous appliquez le mécanisme de manière cohérente aux frontières de votre serveur : lire une fois les headers à l’entrée de la requête, traduire les exceptions de sortie en une réponse stable. Selon l’hébergement (p. ex. RO-HTTP-Server, IIS-Hosting, déployé en interne Windows-/Windows- et Linux-Services) diffèrent les points d’accroche concrets ; le principe reste le même : construire le contexte, appeler la logique métier, mapper les exceptions de manière centrale.
Dans les projets RemObjects, on travaille souvent méthode de service par méthode. Cela fonctionne bien au départ, mais se dégrade en exploitation : chaque méthode implémente le logging et la gestion des erreurs différemment. Un découpage propre est une Service-Basis ou un Dispatcher qui standardise.
Processus pratique (volontairement concis et proche de l’implémentation)
- Lire la Correlation-ID depuis l’en-tête de requête
X-Correlation-ID; si elle manque, la générer côté serveur (p. ex. GUID). - Lire la version du contrat depuis
Accept(ou depuisX-Api-Version). - Logger le démarrage 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 l’en-tête de réponse
X-Correlation-ID. - Logger 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 marginal fréquent lié à Delphi : la méthode de service déclenche du travail asynchrone (p. ex. génération de rapports, import, push vers un DMS). Alors, le thread initial de la requête n’est plus celui qui écrira les lignes de log ultérieurement. Si la Correlation-ID n’est connue que « au départ », la traçabilité se délite.
Règle pragmatique : tout ce qui ne reste pas strictement dans le thread de la requête reçoit le contexte passé explicitement. Même si cela alourdit les listes de paramètres, c’est rentable. En alternative, on peut travailler avec un objet de contexte clairement défini, qui est délibérément passé aux workers (plutôt que d’utiliser 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-Verbindungen ne sont pas automatiquement partageables de manière thread-safe. Un pool de connexions ou une connexion par thread est souvent plus judicieux qu’une « connexion globale ».
- Bornes de transaction : Si vous avez, au sein d’une requête, plusieurs étapes qui appartiennent à une même unité logique, la transaction doit rester dans cette même unité logique. Du travail asynchrone ne doit pas « se poursuivre par erreur » dans la même transaction.
- Annulation : Quand le client interrompt (timeout du proxy, fermeture du navigateur), le serveur continue souvent de tourner. Réfléchissez consciemment à l’intérêt du travail en arrière-plan 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 mappage des erreurs propre est plus que cosmétique. Il détermine si un interlocuteur (connecteur ERP, travail ETL, portail client) peut réagir correctement. Quelques règles pratiques qui se sont montrées efficaces dans les environnements Delphi/RemObjects :
- 400 Bad Request : validation, paramètres manquants/invalides, JSON non analysable. Important : la réponse doit rester stable, même si le corps est corrompu.
- 401/403 : séparez authentification et autorisation. 401 signifie « identité absente/invalide », 403 « identité ok, mais action interdite ».
- 404 : la ressource n’existe pas. Prudence côté sécurité : il n’est pas toujours souhaitable de révéler l’existence d’un objet.
- 409 Conflict : conflit métier (p. ex. conflit de version, « le statut n’autorise pas cette action », violation de clé unique lorsque cela a une pertinence métier).
- 422 Unprocessable Content : quand 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 classifier proprement. Cela inclut aussi « base de données indisponible », « délai d’attente », « exception non gérée ».
Astuce spécifique Delphi : de nombreuses erreurs de DB remontent sous forme d’exceptions génériques. Il vaut la peine, à la couche d’accès aux données, d’identifier délibérément les situations connues et de les transformer en EApiError. Important : n’intégrez pas de fragments SQL ni de noms internes de tables/colonnes dans le message destiné au client. Ces détails appartiennent au log, pas à la réponse.
Astuce de debug : erreurs reproductibles via un « Contract Snapshot »
Inhabituel, mais extrêmement utile en production : sauvegardez en cas d’erreurs (ou de manière ciblée pour certaines Correlation-IDs) un « snapshot » composé des en-têtes de requête + corps de requête dans un fichier de spool de debug. Ce n’est pas du logging permanent (protection des données/volume), mais un outil contrôlé pour reproduire des cas difficiles proches de la production.
Important : un snapshot ne doit jamais persister sans filtre les auth-headers, tokens ou données personnelles. En pratique cela implique : redaction (masquage) et activation uniquement via feature-flag ou liste blanche (p. ex. seulement pour certaines ID de corrélation, sur des fenêtres temporelles courtes).
Implémentation propre en pratique : masquer plutôt que supprimer
Dans les intégrations réelles, les champs « critiques » sont souvent ceux qui seraient utiles pour le debug (p. ex. des identifiants). Plutôt que de tout supprimer systématiquement, masquez : remplacez partiellement les tokens, conservez uniquement le domaine d’une adresse e-mail, n’enregistrez que les derniers chiffres d’une IBAN. Ainsi le cas reste reproductible sans disséminer des données sensibles 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 se termine rarement directement côté client. Typiquement, on trouve des chaînes composées de reverse proxy, de terminaison TLS, de WAF ou d’API-Gateway. Cela entraîne des points pratiques :
- IP distante : Ne vous fiez pas aveuglément à
X-Forwarded-For. Ne reprendre les valeurs que depuis des proxies de confiance, sinon utilisez l’IP socket directe. Les manuels d’exploitation doivent préciser quels sauts sont considérés comme de confiance. - Timeouts : Si le proxy dispose de 30 secondes mais que votre backend a besoin de 2 minutes, vous générez des requêtes fantômes. Fixez des timeouts de manière cohérente le long de la chaîne et décidez : requête synchrone ou pattern job (202 Accepted + point de terminaison d’état).
- Correlation-ID : Inscrivez la Correlation-ID dans les en-têtes de réponse, afin que les administrateurs puissent la corréler entre les logs et le côté client. Si une gateway génère ses propres Request-IDs : consignez et mettez en correspondance les deux IDs.
- Messages d’erreur : En production, pas de détails internes. Détails de debug uniquement de manière contrôlée (staging / feature-flag) et, à défaut, consignés uniquement dans les logs.
Mise en perspective : pourquoi RemObjects SDK peut être avantageux ici
Dans les écosystèmes Delphi, les REST-Server sont souvent construits avec des frameworks légers (p. ex. des routeurs HTTP minimalistes). RemObjects SDK montre sa force quand vous avez déjà ou avez besoin d’une architecture multicouche :
- Limites de service claires : les méthodes de service sont explicites, les contrats peuvent être versionnés.
- Transports et sérialisation : vous pouvez communiquer en JSON, mais aussi en d’autres formats de message (selon la configuration), 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 déploiements propres.
L’approche présentée complète cela par les éléments souvent absents au quotidien : objets d’erreur uniformes, versionnement déterministe et journalisation 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 démarche bascule-t-elle ?
La valeur ajoutée apparaît lorsque votre interface REST ne se contente pas de « fonctionner », mais est exploitable sur le long terme : contrats JSON stables, versionnement sans prolifération d’URL, erreurs traçables et débogage sans tâtonnements. C’est précisément là que l’approche avec Context, Correlation-ID et le mappage centralisé des exceptions dans RemObjects SDK est efficace.
Limites d’utilisation : Si vous n’avez qu’un seul endpoint éphémère sans partenaire d’intégration, le versionnement par Media-Type ressemble vite à de l’overengineering. Le snapshot-logging n’a de sens que si vous implémentez de manière disciplinée la redaction et l’activation. Et : si votre pile de proxies « optimise » ou supprime des en-têtes, vous devez d’abord redresser l’infrastructure, sinon vous déboguez la mauvaise couche.
Si vous modernisez un parc de serveurs Delphi existant ou que vous devez intégrer proprement une solution logicielle proche des processus dans un ERP/DMS/CRM, ces mécanismes sont souvent la différence entre « fonctionne en test » et « fonctionne en production ».
Dans le contexte 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 les évolutions doivent fonctionner de manière cohérente.
Étape suivante
Lorsque ce sujet devient un projet concret, 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 repoussés en tant que conséquences ultérieures.
- Vous identifiez tôt quelle voie est viable sur le plan économique et opérationnel.