Net-Base Revista

09.06.2026

REST API con RemObjects SDK: versionado y depuración limpias de endpoints JSON (Delphi fragmento de código fuente)

Cómo construir con RemObjects SDK en Delphi una API REST que no falle en producción: contratos JSON estables, versionado sin proliferación de URLs, Correlation-ID a través de todas las capas, mapeo centralizado de errores, Snapshot-Logging para casos complejos de depuración y pautas prácticas.

09.06.2026

Del tema de la revista a la práctica del proyecto

Páginas de servicios y técnicas relacionadas

Por qué «REST API con RemObjects SDK» en la práctica suele decidirse en los márgenes

Una REST API con RemObjects SDK rara vez se gana o pierde por el servicio de «Hello World», sino en los puntos donde operación, legado e integración confluyen: versionado sin interrupciones, comportamiento de errores consistente en todos los endpoints, depuración reproducible en cadenas de proxy y la capacidad de correlacionar de forma inequívoca las solicitudes en caso de incidencia.

RemObjects SDK aporta para ello mucha infraestructura: servicios, formatos de mensaje, serialización, hosting (p. ej. como Windows- y Linux-Services o tras IIS/Reverse Proxy) y puntos definidos para manejar errores de forma central. Lo que con frecuencia falta en paisajes de software empresarial maduros es un contrato aplicado de forma consistente: ¿Qué campos JSON son estables? ¿Cómo señalamos los errores? ¿Cómo volvemos a identificar una petición cuando ha atravesado balanceadores de carga, terminación TLS y varias capas de backend?

El siguiente enfoque (incluye Delphi-fragmentos) muestra una línea robusta para RemObjects SDK: versionar contratos JSON, imponer Correlation-ID (ID de solicitud para seguimiento), traducir excepciones a códigos de estado HTTP y a objetos de error JSON y no enfrentar depuración y operación entre sí. Además examinamos casos límite que ocurren con regularidad en entornos reales: gestión de hilos en el servidor, accesos a base de datos con reemplazo de BDE y conexión nativa, cabeceras de proxy, timeouts y payloads de cliente «sucios».

Decisión arquitectónica: versionado por tipo de medio en lugar de URL

Muchas APIs versionan mediante rutas como /v1/. Es pragmático, pero en integraciones de larga duración (p. ej. integraciones ERP/DMS/CRM) a menudo conduce a duplicación de URLs, rutas duplicadas, pruebas duplicadas y al «¿qué versión usamos en realidad?» en los manuales de operación.

Una alternativa es el versionado mediante el Media Type (Content Negotiation). El cliente envía p. ej. Accept: application/vnd.company.order+json;v=2. El servidor extrae la versión de forma determinista y adapta el comportamiento del contrato/DTO. Esto funciona en cadenas de proxy y cache si los encabezados se reenvían correctamente. Para los administradores además es fácilmente verificable: una petición se puede reproducir con Curl/Postman sin que cambien las URLs.

RemObjects SDK no es «REST-purista», sino un framework de servicios pragmático. Precisamente por eso merece la pena la variante por tipo de medio: puede mantener endpoints estables y, aun así, evolucionar contratos. Es importante que siempre evalúe la versión, que la decida de forma centralizada y que transfiera el resultado al contexto de servicio.

¿Cuándo falla la variante de Accept-Header?

En la práctica hay tres puntos de fallo típicos que conviene abordar de antemano:

  • Políticas de proxy: Algunas reglas de Reverse Proxy/WAF normalizan o filtran el header Accept. Entonces su API cae silenciosamente a la versión por defecto. Solución: comprobar explícitamente las reglas del proxy y, si procede, recurrir a X-Api-Version.
  • Bibliotecas cliente: Algunos clientes HTTP establecen sus propios headers Accept y sobreescriben valores. Solución: soportar la versión del contrato también como parámetro de consulta opcional (solo como fallback), o parsear el header Accept en el servidor de forma tolerante.
  • Caching: Si hay caché de respuesta en juego, la 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 conscientemente Vary o desactivar el caché a nivel de la API.
  • Fragmento de código: Request-Context, Correlation-ID, Version y Error-Mapping

    El código está deliberadamente diseñado para integrarse en proyectos de servidor RemObjects existentes: 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 solicitud, que aparece de nuevo en la respuesta y se referencia en los registros.
    • Exception-Mapping: Traducción de excepciones internas Delphi a objetos de error estables, procesables por el cliente (incl. estado HTTP).
    • Contract-Version: Versión del contrato JSON que controla el comportamiento y los campos.
    Delphi
    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 petición estable en lugar de „irgendwo im Threadlocal“

    El fragmento separa intencionadamente: TApiContext es el estado mínimo que debe propagarse. En RemObjects SDK mucho funciona mediante el contexto de servidor/canal. En proyectos heterogéneos (p. ej. hilos de trabajo adicionales, cola de base de datos, tareas en segundo plano) la propagación explícita suele ser más robusta que los threadlocals implícitos, porque así se hacen más visibles la concurrencia y los cambios de contexto.

    Condiciones: La variante por encabezado Accept requiere que su reverse proxy (nginx, IIS ARR, Traefik) reenvíe el encabezado sin modificar. En algunos entornos los encabezados Accept „poco convencionales“ son filtrados o consolidados.

    Riesgos: Versionar vía Accept solo es tan bueno como sus pruebas. Si los clientes usan librerías que sobrescriben Accept, una API puede volver inesperadamente al valor por defecto. Para clientes legacy es razonable un fallback por defecto, pero debe ser visible en el monitoring (p. ej. advertencia de log „Version defaulted“).

    Variantes: Si prefiere versionar mediante X-Api-Version: el parser es idéntico, solo cambia la fuente. Desde la perspectiva de los gateways a veces es más fácil de controlar.

    Integración en RemObjects SDK: Correlation-ID y mapeo de excepciones en la entrada del servicio

    El efecto real aparece cuando aplica la mecánica de forma consistente en el borde de su servidor: leer una vez en la entrada de la petición desde los encabezados, y traducir una vez en la salida de excepciones a una respuesta estable. Según el hosting (p. ej. RO-HTTP-Server, IIS-Hosting, servicios autoalojados Windows-/Windows- y Linux-Services) varían los puntos concretos de enganche; el principio sigue igual: 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. Al principio escala bien, pero en operación falla: cada método implementa el registro y el manejo de errores de forma distinta. Un corte limpio es una base de servicio o un dispatcher que estandarice.

    Proceso práctico (intencionadamente breve y cercano a la implementación)

    1. Leer la Correlation-ID del encabezado de la petición X-Correlation-ID; si falta, generarla en el servidor (p. ej. GUID).
    2. Leer la versión del contrato desde Accept (o desde X-Api-Version).
    3. Registrar inicio de la petición: método, ruta, Correlation-ID, IP remota, iniciar medición de duración.
    4. Ejecutar la lógica de negocio; encapsular los accesos a la BD de forma transaccional en la medida de lo posible.
    5. Capturar excepciones: determinar el estado HTTP, generar objeto de error JSON, establecer el encabezado de respuesta X-Correlation-ID.
    6. Registrar fin de la petición: estado, duración, y, si procede, código de error.

    Hilos en el servidor: por qué la Correlation-ID resulta inútil sin disciplina de contexto

    Un caso límite común de 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 de la petición original ya no es el que escribirá las líneas de log más tarde. Si la Correlation-ID solo se conoce „al inicio“, la trazabilidad se pierde.

    Regla pragmática: todo lo que no permanezca estrictamente en el hilo de la petición debe recibir el contexto pasado explícitamente. Aunque ello implique listas de parámetros más largas, compensa. Alternativamente puede emplearse un objeto de contexto claramente definido que se pase deliberadamente a los worker (en lugar de variables globales o singletons ocultos).

    Puntos críticos típicos en servidores RemObjects/Delphi:

    • Conexiones a BD por hilo: BDE-Ablosung mit nativer Anbindung-conexiones no son automáticamente seguras para uso concurrente por hilos. Un pool de conexiones o una conexión por hilo suele ser más sensato que «una conexión global».
    • Límites de transacción: Si dentro de una petición tiene varios pasos que pertenecen juntos, la transacción debe permanecer en la misma unidad lógica. Trabajo asíncrono no debe continuar «por accidente» en la misma transacción.
    • Cancelación: Si el cliente aborta (timeout del proxy, navegador cerrado), el servidor a menudo continúa. Piense conscientemente si el trabajo en segundo plano tiene sentido en ese caso.

    Acceso a datos y códigos de error: 409 no es „auch ein 500“

    En proyectos de integración, un mapeo de errores limpio es más que cosmética. Determina si un contraparte (ERP-Connector, ETL-Job, portal de clientes) puede reaccionar correctamente. Algunas pautas prácticas que han demostrado su validez en entornos Delphi/RemObjects:

    • 400 Bad Request: Validación, parámetros faltantes/inválidos, JSON no analizable. Importante: la respuesta debe permanecer estable incluso si el body está corrupto.
    • 401/403: Separe autenticación y autorización. 401 significa «identidad ausente/inválida», 403 «identidad correcta, pero prohibido».
    • 404: El recurso no existe. Precaución con la 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 cuando es relevante desde el punto de vista funcional).
    • 422 Unprocessable Content: Cuando sintácticamente todo está bien, pero falla la validación de negocio (no todos los equipos usan 422, pero a menudo es más claro que 400).
    • 500: Todo lo que no pueda clasificar de forma clara. Incluye también «DB down», «Timeout», «Unhandled Exception».

    Truco específico de Delphi: Muchos errores de BD se elevan como excepciones genéricas. Vale la pena comprobar en la capa de acceso a datos situaciones conocidas y convertirlas a 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 operación: guarde, ante errores (o selectivamente para determinadas Correlation-IDs), un «snapshot» de Request-Headern + Request-Body 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 desde entornos cercanos a producción.

    Importante: un snapshot nunca debe persistir sin filtrar Auth-Header, Tokens o datos personales. En la práctica esto significa: redacción (mascarado) y activación sólo mediante feature-flag o whitelist (p. ej. sólo para determinadas Correlation-IDs, ventanas de tiempo cortas).

    Implementación limpia en la práctica: enmascarar en lugar de eliminar

    En integraciones reales, precisamente los campos «críticos» suelen ser los que necesitaría para depurar (p. ej. identificadores). En lugar de omitirlos de forma general, es mejor enmascararlos: sustituir parcialmente Tokens, conservar sólo el dominio de un e‑mail, dejar sólo los últimos dígitos de la IBAN. Así el caso sigue siendo reproducible sin esparcir datos innecesarios en el sistema de archivos. 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 REST API rara vez termina directamente en el cliente. Lo habitual son cadenas de Reverse Proxy, TLS-Termination, WAF o API-Gateway. De ello se derivan puntos prácticos:

    • IP remota: No confíe ciegamente en X-Forwarded-For. Solo acepte valores de proxies de confianza y, en caso contrario, use la IP del socket directo. En los manuales de operación debe indicarse qué saltos son „trusted“.
    • Timeouts: Si un proxy tiene 30 segundos y su backend necesita 2 minutos, generará solicitudes fantasma. Establezca 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 logs y el lado cliente. Si un gateway genera sus propias Request-IDs: registre y mapee ambas IDs.
    • Mensajes de error: En producción no exponga detalles internos. Detalles de depuración solo de forma controlada (entorno Stage / feature-flag) y, en caso de duda, únicamente en el log.

    Contexto: por qué RemObjects SDK puede tener ventaja aquí

    En ecosistemas Delphi los servidores REST a menudo se construyen con frameworks más ligeros (p. ej. routers HTTP minimalistas). RemObjects SDK muestra su fortaleza cuando ya dispone o necesita una arquitectura en capas:

    • Límites de servicio claros: los métodos de servicio son explícitos y los contratos pueden versionarse.
    • Transportes y serialización: puede comunicarse en JSON, pero también soportar otros formatos de mensaje (según configuración), sin mezclar la lógica de negocio.
    • Operación: las opciones de hosting e integración en servicios existentes de Windows y Linux son previsibles, incluyendo despliegues ordenados.

    El enfoque mostrado añade las piezas que con frecuencia faltan en el día a día: objetos de error uniformes, versionado determinista y logging correlacionable. En software empresarial a medida con largos ciclos de vida, eso ahorra tiempo en actualizaciones y en la integración con sistemas externos.

    Conclusión: ¿compensa el esfuerzo — y dónde deja de ser apropiado el enfoque?

    El valor aparece 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. Justamente ahí el enfoque con Context, Correlation-ID y mapeo centralizado de excepciones en RemObjects SDK es potente.

    Límites de aplicación: Si solo tiene un endpoint único y de corta vida sin partners de integración, la versionación por Media-Type puede ser rápidamente overengineering. El registro de instantáneas solo tiene sentido si implementa de forma disciplinada el enmascaramiento y la activación. Y: si su pila de proxies „optimiza“ o elimina cabeceras, primero debe poner en orden la infraestructura; de lo contrario estará depurando la capa equivocada.

    Si va a modernizar una infraestructura de servidores Delphi existente o necesita integrar de forma ordenada una solución de software cercana al proceso en ERP/DMS/CRM, estos mecanismos suelen marcar la diferencia entre „funciona en pruebas“ y „funciona en operación“.

    En el entorno funcional también desempeñan un papel importante Delphi REST-API y REST-Server y Remobjects Sdk Delphi, cuando las integraciones, los flujos de datos y el desarrollo continuo deben funcionar de forma coherente.

    Conversar sobre el proyecto o la iniciativa de modernización con Net-Base.

    Siguiente paso

    Cuando el tema se convierte en un proyecto real, la arquitectura, los sistemas existentes y la operación deben considerarse desde el principio.

    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 rollout no se posponen como consecuencias tardías.
    • Detecta con antelación qué enfoque es viable desde el punto de vista económico y operativo.

    Compartir entrada

    Compartir esta publicación directamente

    LinkedIn, X, XING, Facebook, WhatsApp y correo electrónico están disponibles de inmediato. Para Instagram preparamos el enlace y un texto breve de inmediato.

    Correo electrónico

    Instagram se abre en una nueva pestaña. El enlace y el texto breve se copian previamente en el portapapeles.