Pular para o conteúdo
Trihelix Tecnologia

Software e integrações

Como integrar dois sistemas sem perder ou duplicar pedidos

Idempotência, padrão outbox, filas com retentativa e DLQ: princípios que evitam pedidos perdidos ou duplicados ao integrar e-commerce, ERP e outros sistemas.

Por Equipe TrihelixPublicado em 6 min de leitura

Nesta página

Integrar uma loja virtual ao ERP, um CRM ao sistema de cobrança ou um aplicativo ao estoque parece simples quando tudo funciona. Os problemas aparecem nas falhas parciais: a rede cai no meio de uma chamada, o sistema de destino responde lento, alguém clica duas vezes. É nesses momentos que pedidos somem ou aparecem em dobro, e a correção vira trabalho manual e retrabalho financeiro.

Por que integrações perdem e duplicam dados

Toda integração entre sistemas passa por uma rede que pode falhar a qualquer momento. O caso mais traiçoeiro não é a falha clara, e sim a ambígua: o sistema A envia o pedido, o sistema B grava, mas a resposta se perde no caminho. Do ponto de vista de A, a chamada falhou. Se A reenviar, o pedido duplica. Se não reenviar e B não tiver gravado, o pedido se perde.

Não existe rede que garanta entrega exatamente uma vez. O que é possível construir é entrega pelo menos uma vez combinada com processamento idempotente no destino. O resultado prático é o mesmo efeito de "exatamente uma vez", sem depender de uma garantia que a infraestrutura não oferece.

ProblemaCausa comumMecanismo que resolve
Pedido duplicadoReenvio após timeout ou duplo cliqueChave de idempotência e restrição de unicidade
Pedido perdidoChamada externa falhou após gravar localmentePadrão outbox com retentativa
Estado inconsistenteGravar no banco e chamar a API sem coordenaçãoOutbox na mesma transação
Fila travada por uma mensagem ruimErro permanente tratado como temporárioLimite de tentativas e DLQ
Ordem trocada de eventosProcessamento paraleloVersão ou sequência por entidade

Idempotência: processar duas vezes com o efeito de uma

Uma operação é idempotente quando executá-la várias vezes produz o mesmo resultado que executá-la uma vez. Para pedidos, isso significa que o destino reconhece uma tentativa repetida e devolve o resultado anterior em vez de criar outro registro.

Escolha da chave

A chave de idempotência precisa identificar a intenção, não a tentativa. Bons candidatos são o identificador do pedido no sistema de origem, combinado com o identificador desse sistema, ou um UUID gerado uma única vez quando o pedido é criado e reutilizado em todas as tentativas. Gerar uma chave nova a cada reenvio anula o mecanismo.

Onde garantir

A verificação "já existe?" seguida de "então insere" falha sob concorrência: duas requisições simultâneas podem passar pela checagem ao mesmo tempo. A garantia deve ficar no banco de dados, com uma restrição de unicidade.

CREATE TABLE pedidos_recebidos (
  id               bigserial PRIMARY KEY,
  origem           text        NOT NULL,
  chave_idempotencia text      NOT NULL,
  payload_hash     text        NOT NULL,
  pedido_id        bigint,
  criado_em        timestamptz NOT NULL DEFAULT now(),
  UNIQUE (origem, chave_idempotencia)
);

-- Tentativa repetida não cria um segundo registro
INSERT INTO pedidos_recebidos (origem, chave_idempotencia, payload_hash)
VALUES ($1, $2, $3)
ON CONFLICT (origem, chave_idempotencia) DO NOTHING
RETURNING id;

Se o INSERT não retornar linha, a chave já foi processada: o sistema consulta o registro existente e devolve o mesmo resultado. Guardar um hash do conteúdo permite detectar o caso em que a mesma chave chega com dados diferentes, o que indica um erro na origem e deve ser rejeitado em vez de ignorado.

O padrão outbox: não perder o que precisa ser enviado

Do lado de quem envia, o erro clássico é gravar o pedido no banco e, em seguida, chamar a API do outro sistema. Se o processo cair entre as duas etapas, o pedido existe localmente mas nunca chega ao destino. Inverter a ordem só troca o problema de lugar.

O padrão outbox resolve isso gravando a intenção de envio na mesma transação da mudança de estado. Um processo separado lê a tabela de saída e faz as chamadas externas.

await db.transaction(async (tx) => {
  const pedido = await tx.pedidos.insert({ clienteId, itens, total });
  await tx.outbox.insert({
    tipo: "pedido.criado",
    agregadoId: pedido.id,
    chaveIdempotencia: `pedido-${pedido.id}`,
    payload: { pedidoId: pedido.id },
  });
});
// Nenhuma chamada HTTP aqui dentro: um worker processa a outbox depois do commit.

Ou as duas gravações acontecem, ou nenhuma. O worker envia cada item com a chave de idempotência, marca como concluído quando recebe confirmação e tenta novamente em caso de falha. Como o destino é idempotente, reenviar não causa duplicidade.

Uma regra importante: nunca chamar sistemas externos dentro da transação do banco. Além de prender conexões e locks durante a latência da rede, isso cria exatamente a situação ambígua que se quer evitar, em que a chamada externa teve efeito mas a transação foi desfeita.

Filas, retentativas e DLQ

Entre a outbox e o destino normalmente há uma fila ou um worker com política de retentativa. Alguns cuidados fazem diferença:

Classificar erros

Erros temporários, como timeout, indisponibilidade ou limite de taxa, devem ser tentados de novo. Erros permanentes, como dados inválidos ou recurso inexistente, não melhoram com retentativa e devem sair do fluxo normal.

Backoff com variação

Retentativas imediatas e sincronizadas sobrecarregam um sistema que já está com problemas. Intervalos crescentes com uma variação aleatória distribuem a carga e dão tempo para a recuperação.

Dead letter queue

Depois de um número máximo de tentativas, a mensagem vai para uma fila de mensagens mortas (DLQ). Ela não bloqueia as demais, fica disponível para análise e pode ser reprocessada depois da correção. Uma DLQ sem monitoramento, porém, é só um lugar onde pedidos desaparecem com mais organização. Alguém precisa ser avisado quando ela cresce.

Quando não reenviar

Há casos em que o resultado de uma chamada é realmente desconhecido e o destino não oferece idempotência, como alguns serviços de envio de mensagens ou de pagamento. Nesses casos, reenviar automaticamente pode gerar efeito duplicado para o cliente final. O mais seguro é marcar o item como ambíguo, consultar o estado no destino quando houver como, e encaminhar para decisão humana.

Checklist para uma integração confiável

  • Cada pedido tem um identificador estável gerado uma vez e usado em todas as tentativas.
  • O destino tem restrição de unicidade sobre origem e chave de idempotência.
  • A intenção de envio é gravada na mesma transação da mudança de estado (outbox).
  • Nenhuma chamada externa acontece dentro de transação de banco.
  • Erros são classificados em temporários e permanentes.
  • Retentativas usam backoff com variação e têm limite.
  • A DLQ é monitorada, com alerta e procedimento de reprocessamento.
  • Resultados ambíguos sem idempotência no destino não são reenviados automaticamente.
  • Logs registram identificadores e status, sem expor dados pessoais ou segredos.
  • Existe uma rotina de reconciliação que compara origem e destino periodicamente.

Reconciliação: a última linha de defesa

Mesmo com tudo bem implementado, vale ter uma verificação independente. Uma rotina que compara, por exemplo, os pedidos do dia na loja com os pedidos registrados no ERP detecta falhas que escaparam dos outros mecanismos, como um bug novo, uma mudança de contrato da API ou uma exclusão manual. A reconciliação não substitui idempotência e outbox, mas transforma falhas silenciosas em alertas.

Como a Trihelix pode ajudar

A Trihelix desenvolve sistemas e integrações entre ERP, CRM, e-commerce e outras plataformas usando os princípios descritos aqui: idempotência, outbox, filas com retentativa, DLQ monitorada e reconciliação. Também revisamos integrações existentes para identificar pontos onde pedidos podem ser perdidos ou duplicados. Cada caso é avaliado conforme os sistemas envolvidos e as limitações das APIs disponíveis. Saiba mais sobre software, APIs e integrações ou fale com a Trihelix.

Leia também

  • Operação e governança6 min de leitura

    Por que documentar o rollback antes de uma mudança

    Por que o plano de retorno deve ser escrito e validado antes de qualquer mudança em produção, com critérios de reversão, responsáveis e um modelo pronto.

Vamos conversar sobre a sua operação?

Conte o cenário atual e o que precisa melhorar. Respondemos com perguntas objetivas e, quando fizer sentido, uma proposta de diagnóstico.