Programação Por Equipe DevBras

Capa do artigo: API de órgão público: separe teste e produção antes do primeiro dado real

API de órgão público: separe teste e produção antes do primeiro dado real

Configure ambientes que não se misturam, recuse URL e credencial trocadas na inicialização, trate instabilidade com espera crescente e registre protocolo sem logar dado de cidadão.

O escritório de contabilidade integrou a consulta de situação cadastral de uma API de governo copiando a URL do exemplo da documentação. Funcionou de primeira. Três meses depois, alguém percebeu que todas as consultas de produção estavam indo para o ambiente de demonstração, que devolve dados fictícios fixos. Os relatórios dos clientes tinham sido montados com resposta de mentira. API de órgão público tem ambiente de teste e de produção, com endereços e credenciais diferentes, e o código precisa recusar a mistura.

A resposta curta: coloque URL, credenciais e ambiente em variáveis de configuração, valide na inicialização que a URL pertence ao ambiente declarado, use tempo limite e novas tentativas com espera crescente só para erros transitórios, guarde o protocolo de cada chamada e nunca registre em log o documento do cidadão por inteiro.

Por que o exemplo do manual não é o endereço de produção

Documentações de API de governo, como as do catálogo do Serpro, costumam oferecer um ambiente de demonstração ou de testes, com credenciais públicas ou de avaliação e dados fictícios. Isso é ótimo para aprender o formato. O problema é que o exemplo do manual quase sempre aponta para esse ambiente, e copiar o exemplo leva o endereço de teste para o código.

O erro contrário é pior. Uma credencial de produção numa máquina de desenvolvimento faz o teste automatizado consumir cota paga e enviar dados de pessoas reais para logs locais. Os dois acidentes têm a mesma causa: o ambiente não é uma decisão explícita do sistema, é um efeito colateral de qual URL alguém colou.

Antes da URL, há o cadastro e o contrato; o guia API de governo pede cadastro cobre essa etapa. Este guia começa quando você já tem as credenciais.

Como separar os ambientes no código

  1. Tire URL e credenciais do código. Variáveis de ambiente ou cofre de segredos, nunca no repositório. A imagem do container também não leva segredo, como mostra o guia de segredo fora da imagem.
  2. Declare o ambiente explicitamente. Uma variável como ORGAO_AMBIENTE com valor teste ou producao.
  3. Valide a combinação na inicialização. Cada ambiente tem uma lista de hosts permitidos. Se a URL não bate, a aplicação nem sobe.
  4. Guarde certificado digital por ambiente. Se a API exige certificado, o de produção fica só no servidor de produção, com acesso restrito.
  5. Mostre o ambiente na tela. Uma faixa "AMBIENTE DE TESTE" no sistema interno evita que alguém trabalhe achando que é produção.
// config-orgao.js
const AMBIENTES = {
  teste: ['api-teste.orgao.exemplo.gov.br'],
  producao: ['api.orgao.exemplo.gov.br']
};

export function carregarConfigOrgao(env = process.env) {
  const ambiente = env.ORGAO_AMBIENTE;
  if (!Object.hasOwn(AMBIENTES, ambiente)) {
    throw new Error('ORGAO_AMBIENTE precisa ser "teste" ou "producao"');
  }
  if (!env.ORGAO_API_URL || !env.ORGAO_CLIENT_ID || !env.ORGAO_CLIENT_SECRET) {
    throw new Error('Faltam ORGAO_API_URL, ORGAO_CLIENT_ID ou ORGAO_CLIENT_SECRET');
  }
  const url = new URL(env.ORGAO_API_URL);
  if (url.protocol !== 'https:' || !AMBIENTES[ambiente].includes(url.hostname)) {
    throw new Error('A URL ' + url.hostname + ' não pertence ao ambiente ' + ambiente);
  }
  return {
    ambiente,
    baseUrl: url.origin,
    clientId: env.ORGAO_CLIENT_ID,
    clientSecret: env.ORGAO_CLIENT_SECRET
  };
}

Os hosts do exemplo são fictícios; os reais estão na documentação da API que você contratou. O ponto é a regra: a aplicação falha na inicialização, e não na primeira consulta de um cliente, quando alguém troca uma variável errada.

Como tratar instabilidade sem parecer um ataque

Serviços de governo têm janelas de manutenção e picos de uso, e algumas APIs limitam requisições por período. Repetir a chamada dez vezes no mesmo segundo quando ela falha aumenta a carga justamente na hora ruim e pode fazer o seu acesso ser bloqueado. O certo é tentar poucas vezes, com espera crescente e um componente aleatório, e só para erros que podem passar sozinhos:

// retry-orgao.js
const esperar = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

function atraso(tentativa) {
  const base = 1000 * 2 ** (tentativa - 1);
  return base / 2 + Math.random() * (base / 2);
}

const TRANSITORIOS = new Set([429, 502, 503, 504]);

export async function chamarComRetry(fazerRequisicao, maxTentativas = 4) {
  for (let tentativa = 1; ; tentativa++) {
    let resp;
    try {
      resp = await fazerRequisicao(AbortSignal.timeout(15000));
    } catch (erro) {
      if (tentativa >= maxTentativas) throw erro;
      await esperar(atraso(tentativa));
      continue;
    }
    if (!TRANSITORIOS.has(resp.status) || tentativa >= maxTentativas) return resp;
    const segundos = Number(resp.headers.get('retry-after'));
    await esperar(segundos > 0 ? segundos * 1000 : atraso(tentativa));
  }
}

Erros 400, 401 e 404 voltam na hora, porque repetir não muda o resultado. O cabeçalho Retry-After, quando vem em segundos, é respeitado. Use isso para consultas; para operações que gravam algo no órgão, só repita se a API documentar como evitar duplicidade. Se a falha persistir, a consulta vai para uma fila e o sistema avisa que o resultado sai mais tarde. O guia de timeout antes do infinito explica por que o tempo limite vem antes de tudo isso.

Log, protocolo e dado do cidadão

A resposta de uma API de governo frequentemente traz um identificador da requisição ou um protocolo. Guarde junto do registro da consulta: é o que o suporte do órgão vai pedir quando você abrir um chamado. O que não vai para o log é o corpo com CPF, nome e situação da pessoa. Registre o mínimo para diagnóstico, com o documento mascarado:

function mascararCpf(cpf) {
  const d = String(cpf).replace(/\D/g, '');
  if (d.length !== 11) return '[cpf inválido]';
  return '***.' + d.slice(3, 6) + '.' + d.slice(6, 9) + '-**';
}

// log: { ambiente: 'producao', rota: '/consulta', status: 200,
//        protocolo: 'abc123', cpf: mascararCpf(cpf), ms: 412 }

O campo ambiente no log parece redundante até o dia em que alguém procura uma consulta de produção nos logs de teste. Mais cuidados de registro estão em log sem senha.

Erros comuns

  • Copiar a URL do exemplo para o código. A produção passa a consultar o ambiente de demonstração e recebe dados fictícios sem erro nenhum.
  • Usar credencial de produção no desenvolvimento. Teste automatizado consome cota paga e espalha dado real por máquinas locais.
  • Repetir a chamada imediatamente em loop. A carga sobe na hora da instabilidade e o acesso pode ser bloqueado.
  • Repetir erros que não são transitórios. Um 401 repetido quatro vezes continua 401 e só atrasa o aviso.
  • Logar o corpo da resposta. CPF e situação cadastral de pessoas ficam em ferramentas de log com acesso amplo.
  • Não guardar o protocolo. O suporte do órgão não consegue localizar a requisição que deu problema.

Checklist

  • URL, credenciais e ambiente vêm de configuração, não do código.
  • A aplicação recusa subir se a URL não pertence ao ambiente declarado.
  • Credenciais e certificado de produção existem só no servidor de produção.
  • Toda chamada tem tempo limite.
  • Novas tentativas só para erros transitórios, com espera crescente.
  • O protocolo ou identificador da requisição é guardado com cada consulta.
  • Logs têm o ambiente e mascaram documentos pessoais.
  • O sistema interno mostra claramente quando está em ambiente de teste.

Para o raciocínio geral de ambientes, veja ambiente de teste que parece produção. E agora procure no seu código: a URL de produção do órgão está numa variável validada na inicialização, ou ainda está no meio de uma função, copiada do manual?

Avalie este artigo

Referência: https://apicenter.estaleiro.serpro.gov.br/documentacao/