Do tema da revista à prática do projeto
Páginas de serviços e técnicas correspondentes ao artigo
Por que „REST API mit RemObjects SDK“ muitas vezes decide na prática nos pontos de fronteira
Uma REST API com RemObjects SDK raramente se define pelo serviço “Hello World”, mas sim nos pontos onde 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 proxies e a capacidade de correlacionar requisições de forma inequívoca em caso de problema.
RemObjects SDK traz muita infraestrutura para isso: serviços, formatos de mensagem, serialização, hosting (por exemplo 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 ambientes de software empresarial estabelecidos é um contrato aplicado de forma consistente: quais campos JSON são estáveis? Como sinalizamos erros? Como identificamos novamente uma requisição depois que ela passou por balanceadores de carga, terminação TLS e várias camadas de backend?
A abordagem a seguir (incluindo trechos do Delphi) apresenta uma linha robusta para RemObjects SDK: versionar contratos JSON, exigir Correlation-ID (Request-ID para rastreamento), traduzir Exceptions para status HTTP e objetos de erro JSON e, assim, não colocar depuração e operação em conflito. Adicionalmente, examinamos casos-limite que ocorrem regularmente em ambientes reais: threading/concorrência no servidor, acessos a banco de dados na migração do BDE para ligação nativa, cabeçalhos de proxy, timeouts e payloads de cliente “sujos”.
Decisão de arquitetura: versionamento por tipo de mídia em vez de URL
Muitas APIs versionam por caminhos como /v1/. Isso é pragmático, mas em integrações de longo prazo (por exemplo integrações com 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 pelo 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 contract/DTO. Isso funciona em cadeias de proxy e cache, desde que os cabeçalhos sejam repassados corretamente. Para administradores é também verificável: uma requisição pode ser reproduzida via curl/Postman sem que as URLs sejam diferentes.
RemObjects SDK não é “REST-puristisch”, mas um framework de serviço pragmático. Exatamente por isso a variante por tipo de mídia compensa: é possível manter endpoints estáveis e ainda assim evoluir contratos. O importante é que você avalie a versão sempre, decida de forma centralizada em um único ponto e incorpore o resultado ao contexto do serviço.
Quando a variante do cabeçalho Accept falha?
Na prática há três pontos típicos de ruptura que devem ser abordados antecipadamente:
- Proxy-Policies: Algumas regras de Reverse Proxy/WAF normalizam ou filtram o cabeçalho Accept. Nesse caso sua API silenciosamente regride para o padrão. Solução: verificar explicitamente as regras do proxy e, se necessário, recorrer a
X-Api-Version. - 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 analisar o cabeçalho Accept no servidor de forma tolerante.
Accept (Vary: Accept), caso contrário entregará a Versão 1 a clientes da Versão 2. Solução: definir conscientemente Vary, ou desativar o caching ao nível da API.Source-Schnipsel: Request-Context, Correlation-ID, Version und Error-Mapping
O código foi intencionalmente estruturado para se integrar em projetos RemObjects-Server existentes: uma pequena camada de Context, um parser para a versão da API (a partir de Accept), um mecanismo de Correlation-ID e um mapeamento central de exceções. Termos:
- Correlation-ID: ID única por request, que reaparece na response e é referenciada nos logs.
- Exception-Mapping: Traduçã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.
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, nenhum detalhe interno, nenhum SQL, nenhum caminho.
// Em debug/stage isto pode ser ampliado 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.Zweck: Stabiler Request-Kontext statt „irgendwo im Threadlocal“
O snippet separa intencionalmente: TApiContext é o estado mínimo que você deve repassar. No RemObjects SDK muito funciona via contexto de servidor/canal. Em projetos heterogêneos (por exemplo, threads de worker adicionais, fila de BD, jobs em segundo plano) repassar explicitamente costuma ser mais robusto do que Threadlocals implícitos, pois isso torna concorrência e trocas de contexto mais visíveis.
Randbedingungen: A variante via Accept-Header pressupõe que seu reverse proxy (nginx, IIS ARR, Traefik) encaminhe o header sem alterações. Em alguns ambientes, headers Accept “incomuns” são filtrados ou agregados.
Stolperfallen: Versionamento via Accept só é tão bom quanto seus testes. Se clientes usarem bibliotecas que sobrescrevem o Accept, uma API pode repentinamente cair para o padrão. Para clientes legados, um fallback padrão faz sentido, mas deve ser visível no monitoramento (p. ex. aviso de log „Version defaulted“).
Varianten: Se preferir versionar via X-Api-Version: o parser é idêntico, só que a origem é outro header. Do ponto de vista de gateways isso às vezes é mais fácil de controlar.
Integration in RemObjects SDK: Correlation-ID und Exception-Mapping am Service-Einstieg
O efeito real ocorre quando você aplica a mecânica konsequent na borda do seu servidor: ler uma vez dos headers na entrada do request, traduzir uma vez na saída de exceção para uma resposta estável. Dependendo do hosting (p. ex. RO-HTTP-Server, IIS-Hosting, selbst betriebener Windows-/Windows- und Linux-Services) os pontos de hook concretos diferem; o princípio permanece: construir o contexto, invocar a lógica de negócio, mapear exceções centralmente.
Em projetos RemObjects costuma-se trabalhar frequentemente por método de serviço. Isso escala bem no início, mas falha em operação: cada método monta logging e tratamento de erros de forma diferente. Um corte limpo é uma Service-Basis ou um Dispatcher que padronize.
Praktischer Ablauf (bewusst kurz und implementierungsnah)
- Ler a Correlation-ID do Request-Header
X-Correlation-ID; se faltar, gerar no servidor (p. ex. GUID). - Ler a Contract-Version do
Accept(ou doX-Api-Version). - Registrar início do request: método, caminho, Correlation-ID, IP remota, iniciar medição de duração.
- Executar a lógica de negócio; encapsular acessos ao BD transacionalmente sempre que possível.
- Capturar exceções: determinar o HTTP-Status, gerar objeto de erro JSON, definir o Response-Header
X-Correlation-ID. - Registrar fim do request: status, duração, eventualmente código de erro.
Threading im Server: Warum Correlation-ID ohne Kontext-Disziplin wertlos wird
Um caso-limite comum em Delphi: o método de serviço dispara trabalho assíncrono (p. ex. geração de relatórios, importação, push para um DMS). Então o thread original do request já não é aquele que escreve as linhas de log posteriormente. Se a Correlation-ID só é conhecida “no início”, a rastreabilidade se perde.
Regra pragmática: tudo o que não permanecer estritamente no request-thread recebe o contexto passado explicitamente. Mesmo que isso pareça aumentar as listas de parâmetros, compensa. Alternativamente, pode-se trabalhar com um objeto de contexto claramente definido, que é deliberadamente passado aos workers (em vez de variáveis globais ou singletons ocultos).
Pontos de inflexão típicos em RemObjects-/Delphi-Servern:
- Conexões DB por thread: BDE-Ablosung mit nativer Anbindung-Verbindungen não são automaticamente seguras para compartilhamento entre threads. Um pool de conexões ou uma conexão por thread costuma ser mais sensato do que “uma conexão global”.
- Limites de transação: Se, dentro de uma requisição, houver vários passos que pertencem juntos, a transação deve permanecer na mesma unidade lógica. Trabalho assíncrono não deve continuar “por engano” dentro da mesma transação.
- Cancelamento: Se o cliente aborta (timeout do proxy, navegador fechado), o servidor frequentemente continua a executar. Avalie conscientemente se trabalho em background ainda faz sentido nesses casos.
Acesso a dados e códigos de erro: 409 não é „também um 500”
Em projetos de integração, um mapeamento de erros limpo é mais do que cosmética. Ele define se a outra parte (ERP-Connector, job ETL, portal do cliente) consegue reagir corretamente. Algumas diretrizes práticas que se mostraram úteis em ambientes Delphi/RemObjects:
- 400 Bad Request: Validação, parâmetros ausentes/inválidos, JSON não parseável. Importante: a resposta deve manter-se estável, mesmo que o corpo esteja corrompido.
- 401/403: Separe autenticação e autorização. 401 significa “identidade ausente/inválida”, 403 “identidade ok, mas proibido”.
- 404: Recurso não existe. Cuidado com segurança: nem sempre convém revelar se algo existe.
- 409 Conflict: Conflito de domínio (por ex. conflito de versão, “estado não permite esta ação”, violação de chave única quando relevante para o domínio).
- 422 Unprocessable Content: Quando sintaticamente tudo está ok, mas a validação de domínio falha (nem toda equipa usa 422, mas muitas vezes é mais claro que 400).
- 500: Tudo aquilo que não consegue classificar adequadamente. Inclui também “DB down”, “Timeout”, “Unhandled Exception”.
Truque específico para Delphi: Muitos erros de BD sobem como exceções genéricas. Vale a pena, na camada de acesso a dados, verificar proativamente situações conhecidas e convertê-las para EApiError. Importante: não incorporar fragmentos SQL ou nomes internos de tabelas/colunas na mensagem ao cliente. Esses detalhes pertencem ao log, não à response.
Dica de debugging: erros reproduzíveis através de „Contract Snapshot”
Incomum, mas extremamente útil em operação: ao ocorrer um erro (ou seletivamente para determinadas Correlation-IDs), grave um “snapshot” composto por cabeçalhos da requisição + corpo da requisição num ficheiro de spool de debug. Isso não é logging permanente (privacidade/volume), mas uma ferramenta controlada para reproduzir casos difíceis a partir do ambiente de produção.
Importante: um snapshot nunca deve persistir sem filtragem headers de autenticação, tokens ou dados pessoais. Na prática isso significa: Redaction (mascaramento) e ativação apenas via feature-flag ou whitelist (por ex. apenas para determinadas Correlation-IDs, janelas de tempo curtas).
Implementação limpa na prática: mascarar em vez de omitir
Em integrações reais, os campos “críticos” são frequentemente os que se precisariam para depurar (por ex. identificadores). Em vez de omitir em bloco, é preferível mascarar: substituir parcialmente tokens, manter apenas o domínio do e‑mail, deixar apenas os últimos dígitos do IBAN. Assim o caso permanece reproduzível, sem espalhar dados desnecessários no sistema de ficheiros. Adicionalmente, o snapshot deve ser claramente identificado como artefato de debug e ter um período de retenção definido.
Segurança e Operação: Encaminhamento de Cabeçalhos, Cadeias de Proxy e Timeouts
Uma REST API raramente termina diretamente no cliente. Tipicamente há cadeias de reverse proxy, terminação TLS, WAF ou API-Gateway. Disso decorrem observações práticas:
- Remote IP: Não confie cegamente em
X-Forwarded-For. Aceite-o apenas de proxies confiáveis e, caso contrário, utilize 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ê gerará requisições fantasmas. Defina timeouts de forma consistente ao longo da cadeia e decida: requisição síncrona ou padrão job (202 Accepted + endpoint de status).
- Correlation-ID: Inclua a Correlation-ID nos cabeçalhos de resposta, para que os administradores possam correlacioná‑la entre logs e o lado cliente. Se um gateway usa IDs de requisição próprias: registre e mapeie ambas as IDs.
- Mensagens de erro: Em produção não exponha detalhes internos. Detalhes de depuração apenas de forma controlada (Stage/Feature-Flag) e, em caso de dúvida, apenas no log.
Contexto: Por que RemObjects SDK pode ser vantajoso aqui
Em ecossistemas Delphi os REST-Server são frequentemente construídos com frameworks mais leves (p. ex. roteadores HTTP minimalistas). RemObjects SDK demonstra sua força quando você já possui ou necessita de uma arquitetura em camadas:
- Limites claros de serviço: Métodos de serviço são explícitos, contratos são versionáveis.
- Transporte e serialização: Você pode usar 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 serviços Windows- e Linux-Services existentes são planejáveis, incluindo implantações ordenadas.
A abordagem demonstrada complementa isso com as partes que costumam faltar no dia a dia: objetos de erro uniformes, versionamento determinístico e logging correlacionável. Especialmente em software empresarial personalizado com longos ciclos de vida, isso lhe economiza tempo em atualizações e na integração de sistemas externos.
Conclusão: Vale o esforço — e onde a abordagem se torna contraproducente?
O valor agregado surge quando sua interface REST não apenas „funciona“, mas é operável de forma sustentável: 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 centralizado de exceções no RemObjects SDK é forte.
Limites de aplicação: Se você tem apenas um único endpoint de curta duração sem parceiros de integração, a Media-Type-Versionierung rapidamente parece overengineering. Também o Snapshot-Logging só faz sentido se você implementar disciplinadamente Redaction e ativação. E: se sua pilha de proxies „otimiza“ ou remove cabeçalhos, primeiro é preciso alinhar a infraestrutura; caso contrário, você estará depurando a camada errada.
Se 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 frequentemente representam a diferença entre „funciona em teste“ e „funciona em produção“.
No âmbito funcional, Delphi REST-API e REST-Server e Remobjects Sdk Delphi desempenham um papel importante, quando integrações, fluxos de dados e a evolução precisam atuar em conjunto de forma ordenada.
Discutir projeto ou iniciativa de modernização com Net-Base.
Próximo passo
Quando o tema se tornar um projeto real, arquitetura, ambiente existente 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 fases posteriores.
- Você identifica cedo qual caminho é viável econômica e operacionalmente.