Net-Base Revista

09.06.2026

REST API com RemObjects SDK: versionar e depurar endpoints JSON de forma limpa (trechos de código-fonte Delphi)

Como construir com RemObjects SDK em Delphi uma API REST que não falhe em produção: contratos JSON estáveis, versionamento sem proliferação de URLs, Correlation-ID por todas as camadas, mapeamento central de erros, Snapshot-Logging para casos de depuração complexos, bem como orientações práticas...

09.06.2026

Do tema da revista à prática do projeto

Páginas de serviços e técnicas correspondentes ao artigo

Por que uma «REST API com RemObjects SDK» frequentemente decide nas bordas na prática

Uma REST API com RemObjects SDK raramente se ganha ou perde no serviço “Hello World”, mas sim nos pontos em que operação, legado e integração colidem: versionamento sem interrupção, comportamento de erro consistente em todos os endpoints, depuração reproduzível em cadeias de proxy e a capacidade de correlacionar pedidos de forma inequívoca em situações problemáticas.

RemObjects SDK fornece muita infraestrutura para isso: serviços, formatos de mensagem, serialização, hosting (por ex. como Windows- e Linux-Services ou atrás de IIS/Reverse Proxy) e pontos definidos para tratamento centralizado de erros. O que frequentemente falta em paisagens de software empresarial crescidas é um contrato aplicado de forma consistente: quais campos JSON são estáveis? Como sinalizamos erros? Como reconhecemos um pedido novamente depois de passar por balanceadores de carga, terminação TLS e várias camadas de backend?

A abordagem a seguir (incluindo um Delphi-snippet) mostra uma linha robusta para RemObjects SDK: versionar contratos JSON, impor Correlation-ID (Request-ID para rastreamento), traduzir Exceptions em status HTTP e objetos de erro JSON e, ao mesmo tempo, não antagonizar depuração e operação. Além disso, examinamos casos-limite que ocorrem regularmente em ambientes reais: threading no servidor, acessos a banco de dados com substituição do BDE com ligação nativa, cabeçalhos de proxy, timeouts e payloads de cliente “sujos”.

Decisão de arquitetura: versionamento via Media Type em vez de URL

Muitas APIs versionam via paths como /v1/. Isso é pragmático, mas em integrações de longa duração (p.ex. conexões ERP/DMS/CRM) frequentemente leva a duplicação de URLs, rotas duplicadas, testes duplicados e à pergunta “qual versão realmente usamos?” nos manuais de operação.

Uma alternativa é versionar via Media Type (Content Negotiation). O cliente envia, por exemplo, Accept: application/vnd.company.order+json;v=2. O servidor lê a versão de forma determinística e ajusta o comportamento do contrato/DTO. Isso funciona em cadeias de proxy e cache, desde que os headers sejam encaminhados corretamente. Para administradores, também é verificável: um pedido pode ser reproduzido via Curl/Postman sem que as URLs variem.

RemObjects SDK não é “REST-purista”, mas sim um framework de serviço pragmático. Por isso a variante de Media Type faz sentido: você mantém endpoints estáveis e ainda assim evolui contratos. É essencial que a versão seja sempre avaliada, decidida de forma centralizada e o resultado seja repassado para o contexto do serviço.

Quando a variante Accept-Header falha?

Na prática há três pontos de falha típicos que deveriam ser abordados antecipadamente:

  • Proxy-Policies: Alguns Reverse Proxies/regras WAF normalizam ou filtram o header Accept. Nesses casos sua API cai silenciosamente para o padrão. Solução: verificar explicitamente as regras do proxy e, se necessário, recorrer a X-Api-Version como alternativa.
  • Client-Libraries: Alguns clientes HTTP definem seus próprios Accept-Header e sobrescrevem valores. Solução: suportar a versão do contrato também como um parâmetro de query opcional (apenas como fallback), ou efetuar um parsing tolerante do Accept-Header no servidor.
  • Caching: Se o cache de respostas estiver em uso, o cache deve variar por Accept (Vary: Accept), caso contrário ele entregará a versão 1 a clientes da versão 2. Solução: definir conscientemente Vary, ou desativar o cache ao nível da API.

Trecho de código-fonte: Contexto de Requisição, Correlation-ID, Versão e Mapeamento de Erros

O código foi deliberadamente estruturado para se integrar a projetos de servidor RemObjects existentes: uma pequena camada de contexto, um parser para a versão da API (a partir do Accept), um mecanismo de Correlation-ID e um mapeamento central de exceções. Termos:

  • Correlation-ID: ID única por requisição, que reaparece na resposta e é referenciada nos logs.
  • Exception-Mapping: Conversão de exceções internas Delphi em objetos de erro estáveis, processáveis pelo cliente (incl. HTTP-Status).
  • Contract-Version: Versão do contrato JSON, que controla comportamento e 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;
// Espera por exemplo: 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;
// Em produção não expor detalhes internos, SQL ou caminhos.
// Em debug/stage isso pode ser estendido via configuração.
begin
  if E is EApiError then
    Exit(E.Message);

  if E is EArgumentException then
    Exit('Parâmetros inválidos.');

  Exit('Erro interno.');
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 estável em vez de “em algum lugar no threadlocal”

O trecho separa propositalmente: TApiContext é o estado mínimo que você deve propagar. No RemObjects SDK muita coisa passa pelo contexto de servidor/canal. Em projetos heterogêneos (p.ex. threads de worker adicionais, fila de BD, jobs em segundo plano) a passagem explícita costuma ser mais robusta do que variáveis thread-local implícitas, porque torna concorrência e trocas de contexto mais visíveis.

Condições: A variante baseada no cabeçalho Accept pressupõe que seu reverse proxy (nginx, IIS ARR, Traefik) reencaminhe o cabeçalho sem alterações. Em alguns ambientes cabeçalhos Accept “não usuais” são filtrados ou consolidados.

Riscos: Versionamento via Accept é tão bom quanto seus testes. Se clientes usarem bibliotecas que sobrescrevem Accept, uma API pode cair inesperadamente para o padrão. Para clientes legados um fallback padrão faz sentido, mas ele precisa ser visível no monitoramento (p.ex. aviso de log “Version defaulted”).

Alternativas: Se você preferir versionar via X-Api-Version: o parser é idêntico, só muda a origem (outro cabeçalho). Do ponto de vista de gateways isso às vezes é mais fácil de controlar.

Integração no RemObjects SDK: ID de correlação e mapeamento de exceções na entrada do serviço

O efeito real surge quando você aplica a mecânica de forma consistente na borda do seu servidor: ler uma vez na entrada do request a partir dos cabeçalhos, traduzir uma vez na saída de exceção para uma resposta estável. Dependendo do hosting (p.ex. RO-HTTP-Server, IIS-Hosting, serviço Windows autogerido-/Windows- e Linux-serviços) os pontos concretos de hook variam; o princípio permanece: construir o contexto, invocar a lógica de negócio, mapear exceções centralmente.

Em projetos RemObjects costuma-se trabalhar por método de serviço diretamente. Isso escala bem no início, mas falha em operação: cada método monta logging e tratamento de erro de forma diferente. Um corte limpo é uma base de serviço ou um dispatcher que padronize isso.

Fluxo prático (propositadamente curto e orientado à implementação)

  1. Ler o ID de correlação do cabeçalho de request X-Correlation-ID; se ausente, gerar no servidor (p.ex. GUID).
  2. Ler a versão do contrato de Accept (ou de X-Api-Version).
  3. Registrar o início do request: método, caminho, ID de correlação, IP remoto, iniciar medição de duração.
  4. Executar a lógica de negócio; encapsular acessos ao BD transacionalmente sempre que possível.
  5. Capturar exceções: determinar o status HTTP, gerar objeto de erro JSON, definir o cabeçalho de resposta X-Correlation-ID.
  6. Registrar o fim do request: status, duração, se aplicável código de erro.

Threading no servidor: por que o ID de correlação sem disciplina de contexto se torna inútil

Um caso de borda comum em Delphi: o método de serviço dispara trabalho assíncrono (p.ex. geração de relatório, importação, push para um DMS). Então o thread original do request não é mais aquele que vai escrever linhas de log depois. Se o ID de correlação for conhecido apenas “no início”, a rastreabilidade se desfaz.

Regra pragmática: tudo que não permaneça estritamente no thread do request deve receber o contexto explicitamente. Mesmo que isso gere listas de parâmetros maiores, compensa. Alternativamente trabalhe com um objeto de contexto bem definido, passado intencionalmente para os workers (em vez de variáveis globais ou singletons ocultos).

Pontos de inflexão típicos em servidores 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.
  • Cancellation: Wenn der Client abbricht (Proxy timeout, Browser closed), 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, portal do cliente) 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.

Segurança e operação: repasse de cabeçalhos, cadeias de proxy e timeouts

Uma API REST raramente termina diretamente no cliente. Tipicamente há cadeias compostas por reverse proxy, TLS-termination, WAF ou API-Gateway. Disso decorrem pontos práticos:

  • Remote IP: Não confie cegamente em X-Forwarded-For. Aceite-o apenas de proxies confiáveis e, caso contrário, use o IP do socket direto. Nos manuais de operação deve constar quais hops são „trusted“.
  • Timeouts: Se o proxy tem 30 segundos, mas seu backend precisa de 2 minutos, você gera requisições „fantasma“. Defina timeouts de forma consistente ao longo da cadeia e decida: requisição síncrona ou padrão de job (202 Accepted + endpoint de status).
  • Correlation-ID: Coloque a Correlation-ID nos cabeçalhos de resposta, para que os administradores possam correlacioná‑la entre logs e do lado do cliente. Se um gateway usar IDs de requisição próprias: registre e mapeie ambas as IDs.
  • Mensagens de erro: Em produção, nada de detalhes internos. Detalhes de depuração somente de forma controlada (Stage/Feature-Flag) e, em caso de dúvida, apenas nos logs.

Contextualização: Por que RemObjects SDK pode ser vantajoso aqui

Em ecossistemas Delphi os REST-Server são frequentemente construídos com frameworks mais leves (por ex. roteadores HTTP minimalistas). O RemObjects SDK mostra sua força quando você já possui ou necessita de uma arquitetura em camadas:

  • Limites de serviço claros: os métodos de serviço são explícitos, os contratos podem ser versionados.
  • Transportes e serialização: Você pode falar JSON, mas também outros formatos de mensagem (conforme o setup), sem misturar a lógica de domínio.
  • Operação: Opções de hospedagem e integração em Windows- e Linux-Services existentes são planejáveis, incluindo rollouts limpos.

A abordagem mostrada complementa isso com as partes que frequentemente faltam no dia a dia: objetos de erro uniformes, versionamento determinístico e logging correlacionável. Especialmente em software empresarial personalizado com ciclos de vida longos, isso economiza tempo em atualizações e na integração de sistemas externos.

Conclusão: Vale o esforço — e onde a abordagem deixa de compensar?

O valor agregado surge quando sua interface REST não apenas „funciona“, mas é operacional a longo prazo: contratos JSON estáveis, versionamento sem proliferação de URLs, erros rastreáveis e depuração sem adivinhação. É exatamente aí que a abordagem com Context, Correlation-ID e mapeamento central de exceções no RemObjects SDK é forte.

Limites de aplicação: Se você tem apenas um único endpoint efêmero sem parceiros de integração, o versionamento por Media-Type rapidamente parece overengineering. O snapshot-logging também só faz sentido se você implementar Redaction e ativação de forma disciplinada. E: se sua pilha de proxies „optimiza“ ou remove cabeçalhos, você precisa primeiro alinhar a infraestrutura; caso contrário, você estará depurando a camada errada.

Se você precisa modernizar uma paisagem de servidores Delphi existente ou integrar de forma limpa uma solução de software próxima ao processo em ERP/DMS/CRM, esses mecanismos são frequentemente a diferença entre „funciona em teste“ e „funciona em operação“.

No âmbito funcional, também desempenham um papel importante Delphi REST-API e REST-Server e Remobjects Sdk Delphi, quando integrações, fluxos de dados e desenvolvimento contínuo precisam interagir de forma consistente.

Discutir projeto ou iniciativa de modernização com Net-Base.

Próximo passo

Quando um tema se torna um projeto real, arquitetura, sistemas existentes e operação devem ser considerados em conjunto desde o início.

Não apenas apoiamos questões pontuais, mas também quando fragmentos de código-fonte, temas legados ou ideias de portais precisam evoluir para um projeto empresarial robusto.

  • Estado atual, estado-alvo e riscos técnicos são avaliados em conjunto.
  • REST, o acesso a dados, os portais e o Rollout não são adiados para uma fase posterior.
  • Você vê cedo qual caminho é economicamente e operacionalmente viável.

Partilhar publicação

Compartilhar esta publicação diretamente

LinkedIn, X, XING, Facebook, WhatsApp e e‑mail estão imediatamente disponíveis. Para o Instagram, preparamos o link e um texto curto de imediato.

E-mail

O Instagram abre numa nova aba. O link e o texto curto são copiados previamente para a área de transferência.