Del tema de la revista a la práctica del proyecto
Páginas de servicios y técnicas relacionadas
Por qué una «REST API con RemObjects SDK» a menudo se decide en los casos límite en la práctica
Una REST API con RemObjects SDK rara vez se define por el servicio “Hello World”, sino por los puntos donde operación, legado e integración chocan: versionado sin detener el sistema, comportamiento de errores consistente en todos los endpoints, depuración reproducible a través de cadenas de proxies y la capacidad de correlacionar de forma inequívoca las peticiones cuando surgen problemas.
RemObjects SDK aporta mucha infraestructura: servicios, formatos de mensajes, serialización, hosting (p. ej. como Windows- y Linux-Services o detrás de IIS/Reverse Proxy) y puntos definidos para manejar errores de forma centralizada. Lo que suele faltar en paisajes de software empresarial maduros es un contrato aplicado de forma consistente: ¿qué campos JSON son estables? ¿cómo señalizamos errores? ¿cómo reconocemos una petición cuando atraviesa balanceadores de carga, terminación TLS y múltiples capas backend?
El siguiente enfoque (incluyendo un Delphi-snip) muestra una línea robusta para RemObjects SDK: versionar contratos JSON, imponer Correlation-ID (ID de solicitud para seguimiento), traducir excepciones a estado HTTP y objetos de error JSON y, al mismo tiempo, no enfrentar depuración y operación. Además examinamos casos límite que aparecen regularmente en entornos reales: threading en el servidor, accesos a base de datos con BDE-Ablösung con enlace nativo, cabeceras de proxy, timeouts y cargas útiles de cliente “sucias”.
Decisión arquitectónica: versionado mediante tipo de medio en lugar de la URL
Muchas APIs versionan mediante rutas como /v1/. Es pragmático, pero en integraciones a largo plazo (p. ej. conexiones ERP/DMS/CRM) con frecuencia conduce a duplicación de URLs, rutas duplicadas, pruebas duplicadas y a la pregunta «¿qué versión usamos realmente?» en los manuales de operación.
Una alternativa es el versionado por tipo de medio (Content Negotiation). El cliente envía, por ejemplo, Accept: application/vnd.company.order+json;v=2. El servidor extrae la versión de forma determinista y adapta el contrato/DTO en consecuencia. Esto funciona en cadenas de proxies y cachés si los encabezados se reenvían correctamente. Para los administradores además es verificable: una petición puede reproducirse con Curl/Postman sin que varíen las URLs.
RemObjects SDK no es «REST-puristisch», sino un framework de servicios pragmático. Precisamente por eso merece la pena la variante de tipo de medio: pueden mantener endpoints estables y, aun así, evolucionar contratos. Es importante evaluar la versión siempre, decidir de forma centralizada y volcar el resultado en el contexto del servicio.
¿Cuándo falla la variante de encabezados Accept?
En la práctica hay tres puntos habituales de ruptura que conviene abordar de antemano:
- Políticas de proxy: algunos Reverse Proxies/reglas WAF normalizan o filtran los encabezados Accept. Entonces su API cae silenciosamente al valor por defecto. Solución: revisar explícitamente las reglas del proxy y, si procede, recurrir a
X-Api-Versioncomo alternativa. - Librerías cliente: algunos clientes HTTP establecen sus propios encabezados Accept y sobrescriben valores. Solución: soportar la versión del contrato también como parámetro de consulta opcional (solo como fallback), o parsear tolerante del lado servidor el encabezado Accept.
- Caché: Si el caché de respuestas está activo, el caché debe variar según
Accept(Vary: Accept), de lo contrario entregará la versión 1 a clientes de la versión 2. Solución: establecer conscientementeVary, o desactivar el caché a nivel de API.
Fragmento de código fuente: contexto de la solicitud, Correlation-ID, versión y mapeo de errores
El código está intencionadamente estructurado para integrarse en proyectos existentes de servidor RemObjects: una pequeña capa de contexto, un parser para la versión de la API (desde Accept), un mecanismo de Correlation-ID y un mapeo central de excepciones. Términos:
- Correlation-ID: ID única por petición, que aparece de nuevo en la respuesta y se referencia en los logs.
- Mapeo de excepciones: Traducción de excepciones internas Delphi a objetos de error estables y manejables por el cliente (incl. estado HTTP).
- Versión del contrato: Versión del contrato JSON que controla el comportamiento y los campos.
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ósito: Contexto de request estable en lugar de “en algún threadlocal”
El fragmento separa deliberadamente: TApiContext es el estado mínimo que desea pasar. En RemObjects SDK muchas operaciones dependen del contexto de servidor/canal. En proyectos heterogéneos (p. ej. hilos de trabajo adicionales, cola de DB, trabajos en segundo plano) pasar el contexto de forma explícita suele ser más robusto que los threadlocals implícitos, porque hace más visibles la concurrencia y los cambios de contexto.
Condiciones: La variante basada en el header Accept requiere que su reverse proxy (nginx, IIS ARR, Traefik) reenvíe la cabecera sin modificar. En algunos entornos, cabeceras Accept “poco comunes” se filtran o se consolidan.
Peligros: Versionar vía Accept solo es tan fiable como sus pruebas. Si los clientes usan librerías que sobrescriben Accept, una API puede volver súbitamente al valor por defecto. Para clientes legacy es razonable tener un fallback por defecto, pero debe ser visible en el monitoring (p. ej. una entrada de log con la advertencia “Version defaulted”).
Variantes: Si prefiere versionar mediante X-Api-Version: el parser es idéntico, solo cambia la cabecera origen. Desde la perspectiva de los gateways eso a veces es más fácil de controlar.
Integración en RemObjects SDK: Correlation-ID y Exception-Mapping en la entrada del servicio
El efecto real ocurre cuando aplica la mecánica de forma consistente en el borde de su servidor: una vez leer desde las cabeceras en la entrada del request, y una vez traducir en la salida de excepciones a una respuesta estable. Según el hosting (p. ej. RO-HTTP-Server, IIS-Hosting, servicios auto-gestionados Windows-/Windows- y Linux-Services) varían los puntos concretos de hook; el principio es el mismo: construir el contexto, invocar la lógica de negocio, mapear las excepciones de forma centralizada.
En proyectos RemObjects a menudo se trabaja directamente por método de servicio. Eso escala bien al principio, pero falla en operación: cada método implementa logging y manejo de errores de forma distinta. Una separación limpia es una base de servicio o un dispatcher que estandarice.
Flujo práctico (intencionadamente breve y cercano a la implementación)
- Leer Correlation-ID desde la cabecera
X-Correlation-ID; si falta, generarla en el servidor (p. ej. GUID). - Leer la versión del contrato desde
Accept(o desdeX-Api-Version). - Registrar inicio del request: método, ruta, Correlation-ID, IP remota; iniciar medición de duración.
- Ejecutar la lógica de negocio; encapsular los accesos a DB preferiblemente en transacciones.
- Capturar excepciones: determinar el estado HTTP, generar un objeto de error JSON, establecer la cabecera de respuesta
X-Correlation-ID. - Registrar fin del request: estado, duración, si procede código de error.
Concurrencia en el servidor: por qué la Correlation-ID se vuelve inútil sin disciplina de contexto
Un caso límite frecuente en Delphi: el método de servicio desencadena trabajo asíncrono (p. ej. generación de informes, importación, push a un DMS). Entonces el hilo original del request ya no es el que escribe las líneas de log posteriormente. Si la Correlation-ID solo se conoce “al principio”, la trazabilidad se rompe.
Regla pragmática: todo lo que no permanezca estrictamente en el hilo del request debe recibir el contexto de forma explícita. Aunque parezca que añade más parámetros, compensa. Alternativamente puede trabajar con un objeto de contexto claramente definido que se pase a los workers de forma deliberada (en lugar de variables globales o singletons ocultos).
Puntos de inflexión típicos en servidores RemObjects/Delphi:
- Conexiones DB por hilo: 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“.
- Límites de transacción: Si dentro de una solicitud tiene varios pasos que pertenecen juntos, la transacción debe permanecer en la misma unidad lógica. El trabajo asíncrono no debe continuar „por accidente“ en la misma transacción.
- Cancelación: Si el cliente interrumpe (timeout del proxy, navegador cerrado), el servidor suele continuar. Valore conscientemente si el trabajo en segundo plano sigue teniendo sentido.
Acceso a datos y códigos de error: 409 no es „también un 500“
En proyectos de integración, un mapeo de errores limpio es más que cosmética. Determina si la contraparte (conector ERP, job ETL, portal de clientes) puede reaccionar correctamente. Unas pautas prácticas que se han probado en entornos Delphi/RemObjects:
- 400 Bad Request: Validación, parámetros faltantes/ inválidos, JSON no parseable. Importante: la respuesta debe permanecer estable incluso si el cuerpo está corrupto.
- 401/403: Separar autenticación y autorización. 401 significa „sin identidad/identidad inválida“, 403 „identidad válida, pero prohibido“.
- 404: Recurso inexistente. Precaución en materia de seguridad: no siempre revelar si algo existe.
- 409 Conflict: Conflicto de negocio (p. ej. conflicto de versiones, „el estado no permite esta acción“, violación de clave única si es relevante desde el dominio).
- 422 Unprocessable Content: Cuando sintácticamente todo está bien, pero falla la validación de negocio (no todos los equipos usan 422, pero suele ser más claro que 400).
- 500: Todo aquello que no pueda clasificarse claramente. Incluye también „DB down“, „Timeout“, „Unhandled Exception“.
Truco específico de Delphi: Muchos errores de DB aparecen como excepciones genéricas. Merece la pena que la capa de acceso a datos detecte explícitamente las situaciones conocidas y las convierta en EApiError. Importante: no incluir fragmentos SQL ni nombres internos de tablas/columnas en el mensaje al cliente. Esos detalles pertenecen al log, no a la respuesta.
Truco de depuración: errores reproducibles mediante „Contract Snapshot“
Poco habitual, pero extremadamente útil en producción: guarde, al producirse errores (o de forma selectiva para determinadas Correlation-IDs), un „snapshot“ con cabeceras de la solicitud + cuerpo de la solicitud en un archivo de spool de depuración. Esto no es un logging permanente (protección de datos/volumen), sino una herramienta controlada para reproducir casos difíciles en entorno cercano a producción.
Importante: un snapshot nunca debe persistir sin filtrar Auth-Header, tokens o datos personales. En la práctica eso significa: redacción (enmascaramiento) y activación solo mediante feature-flag o whitelist (p. ej. solo para determinadas Correlation-IDs, ventanas de tiempo cortas).
Implementación limpia en la práctica: enmascarar en vez de omitir
En integraciones reales, los campos „críticos“ suelen ser precisamente los que se necesitarían para depurar (p. ej. identificadores). En lugar de omitirlos de forma general, es preferible enmascararlos: sustituir parcialmente tokens, conservar solo el dominio del correo electrónico, mantener solo las últimas cifras del IBAN. Así el caso sigue siendo reproducible sin dispersar datos innecesarios en el sistema de ficheros. Además, el snapshot debe estar claramente marcado como artefacto de depuración y tener un periodo de retención definido.
Seguridad y operación: reenvío de cabeceras, cadenas de proxy y timeouts
Una API REST rara vez termina directamente en el cliente. Lo habitual son cadenas de reverse proxy, terminación TLS, WAF o API-Gateway. De ello se derivan puntos prácticos:
- IP remota: No confíe ciegamente en
X-Forwarded-For. Solo acepte ese encabezado desde proxies de confianza; en caso contrario, utilice la IP del socket directo. En los manuales de operación debe indicarse qué saltos son „trusted“. - Timeouts: Si el proxy tiene 30 segundos pero su backend necesita 2 minutos, generará solicitudes fantasma. Establezca los timeouts de forma coherente a lo largo de la cadena y decida: petición síncrona o patrón de job (202 Accepted + endpoint de estado).
- Correlation-ID: Incluya la Correlation-ID en los encabezados de respuesta para que los administradores puedan correlacionarla entre los logs y el lado cliente. Si un gateway usa IDs de petición propias: registre y relacione ambas IDs.
- Mensajes de error: En producción no muestre detalles internos. Los detalles de depuración solo de forma controlada (stage/feature-flag) y, en caso de duda, únicamente en los logs.
Valoración: por qué RemObjects SDK puede resultar ventajoso aquí
En los ecosistemas Delphi los REST-servidor a menudo se construyen con frameworks más ligeros (p. ej., enrutadores HTTP minimalistas). RemObjects SDK muestra su fortaleza cuando ya dispone de una arquitectura por capas o la necesita:
- Límites de servicio claros: los métodos de servicio son explícitos y los contratos son versionables.
- Transportes y serialización: puede usar JSON, pero también otros formatos de mensaje (según el entorno), sin mezclar la lógica de negocio.
- Operación: las opciones de hosting y la integración en servicios existentes Windows- y Linux-servicios son planificables, incluyendo despliegues limpios.
El enfoque mostrado lo complementa con las partes que a menudo faltan en el día a día: objetos de error uniformes, versionado determinista y logging correlacionable. Especialmente en software empresarial a medida con largos ciclos de vida, esto le ahorra tiempo en actualizaciones y en la integración de sistemas externos.
Conclusión: ¿Compensa el esfuerzo — y dónde no funciona el enfoque?
El valor añadido surge cuando su interfaz REST no solo „funciona“, sino que es operable a largo plazo: contratos JSON estables, versionado sin proliferación de URLs, errores trazables y depuración sin adivinanzas. Es precisamente ahí donde el enfoque con Context, Correlation-ID y mapeo central de excepciones en RemObjects SDK es sólido.
Límites de aplicación: Si solo dispone de un único endpoint efímero sin socios de integración, la versionación por Media-Type puede convertirse rápidamente en sobreingeniería. El Snapshot-Logging tampoco tiene sentido salvo que implemente de manera disciplinada el enmascaramiento (redaction) y la activación. Y: si su pila de proxies „optimiza“ o elimina cabeceras, primero debe alinear la infraestructura; de lo contrario, depurará la capa equivocada.
Si va a modernizar una infraestructura de servidores Delphi existente o necesita integrar de forma limpia una solución de software orientada a procesos en ERP/DMS/CRM, estos mecanismos suelen ser la diferencia entre „funciona en pruebas“ y „funciona en producción“.
En el ámbito funcional, Delphi REST-API y REST-servidor y Remobjects Sdk Delphi desempeñan un papel importante cuando las integraciones, los flujos de datos y la evolución deben operar de forma coordinada.
Discutir un proyecto o una iniciativa de modernización con Net-Base.
siguiente paso
Cuando un tema se convierte en un proyecto real, arquitectura, entorno existente y operación deben considerarse conjuntamente desde el inicio.
No solo apoyamos en consultas puntuales, sino también cuando, a partir de fragmentos de código fuente, temas heredados o ideas de portales, debe consolidarse un proyecto empresarial robusto.
- La situación actual, el estado objetivo y los riesgos técnicos se evalúan conjuntamente.
- REST, el acceso a datos, los portales y el despliegue no se relegan a fases posteriores.
- Usted detecta con antelación qué camino es viable, tanto económica como operativamente.