Dev & EngARTIGO

A importância de sua API suportar um ID de rastreamento

A importância de sua API suportar um ID de rastreamento
Imagem: Cesar Gimenes

Esse não é o TraceID (ou trace_id) usado no tracing distribuído. O TraceID identifica um trace e é propagado entre os serviços; ele pode, inclusive, chegar à sua API vindo do cliente. E tracing também funciona com filas, como mostram as convenções do OpenTelemetry para mensageria.

Mas, se sua API é assíncrona, processando eventos em filas, você precisa de um ID de rastreamento da operação para correlacionar eventos que pertencem ao mesmo job, mesmo quando passam por traces diferentes. Nesse contrato, em vez de ser gerado na entrada da API, o ID de rastreamento é obrigatoriamente gerado e enviado pelo cliente, seguindo as regras de geração de ID que você definir. O cliente precisa salvar esse ID antes do primeiro envio e reutilizá-lo nas novas tentativas da mesma operação.

Um exemplo claro são sistemas que criam ordens de pagamento. O cliente envia requisições para a API criar essas ordens, e cada ordem tem um ID de rastreamento único, gerado pelo cliente. Esse ID é então usado para correlacionar todos os eventos relacionados àquela ordem. Por exemplo, seu sistema gerou a ordem, mandou direitinho para a API, e a API fez apenas uma validação básica e colocou a ordem na fila para ser processada.

A partir daí, você vai receber eventos via webhook ou algum outro mecanismo para saber o estado da ordem e então ajustar o status dela no seu sistema. Ela pode ser aprovada, rejeitada, cancelada, paga etc. Todos esses eventos vão ter o mesmo ID de rastreamento que foi enviado por você na requisição inicial.

O problema é que, se sua API não suporta um ID de rastreamento e houver algum problema na comunicação, o cliente pode ficar sem saber se a requisição foi aceita. A API pode ter registrado a ordem e até começado a processá-la; quem ficou sem confirmação foi o cliente. Por exemplo, se seu servidor estiver sobrecarregado e não conseguir responder a tempo, o cliente vai receber um timeout. E, se a requisição foi aceita, o cliente não recebeu o ID da ordem que foi criada para poder acompanhar o status dela.

É por isso que o ID precisa ser gerado pelo cliente, antes do envio, e não pela sua API. Você pode aceitar a requisição e não conseguir devolver o ID ao cliente. Sem saber o que aconteceu, ele pode tentar novamente e criar outra ordem para o mesmo pagamento.

Timeout não significa que a operação falhou.

Aqui tem um cliente típico em GoGo23 conteúdosEntendendo o Green Tea GC do Go 1.26Dev (Back & Front) · mai 2026Função recursiva em Go para acessar valores em mapas aninhadosDev (Back & Front) · set 2025Publicando projeto desenvolvido em Golang em um server grátisDev (Back & Front) · mar 2025Ver tudo em Dev (Back & Front) que faz uma requisição POST para uma API e espera por uma resposta. A URL, o token e o ID são fictícios; o tracking_id representa o ID que o cliente já gerou e salvou para essa ordem. Neste exemplo, a API responde com 202 Accepted quando aceita a ordem para processamento.

go
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"time"
)

func main() {
	err := createOrder()
	if err != nil {
		log.Fatal(err)
	}
}

func createOrder() error {
	client := &http.Client{
		Timeout: 5 * time.Second,
	}

	req, err := http.NewRequest(
		http.MethodPost,
		"https://example.com/api/v1/orders",
		strings.NewReader(`{"tracking_id":"order-123","amount":1000}`),
	)
	if err != nil {
		return fmt.Errorf("error creating request: %w", err)
	}

	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer ...")

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("error on request: %w", err)
	}

	body, err := io.ReadAll(resp.Body)
	closeErr := resp.Body.Close()
	if err != nil {
		return fmt.Errorf("error reading response: %w", err)
	}
	if closeErr != nil {
		return fmt.Errorf("error closing response: %w", closeErr)
	}
	if resp.StatusCode != http.StatusAccepted {
		return fmt.Errorf("unexpected response: %s", resp.Status)
	}

	log.Printf("body: %s", body)
	return nil
}

Note que eu coloquei um timeout de 5 segundos para a requisição. 5 segundos é uma eternidade para uma API que só precisa validar e enfileirar uma ordem. Mas o cliente precisa colocar algum limite aí. No Go, o http.Client.Timeout inclui a conexão, os redirecionamentos e a leitura do corpo da resposta. O valor zero significa que o cliente não impõe um limite total. Sem timeout ou um prazo no contexto da requisição, ela pode ficar esperando indefinidamente, consumindo recursos. Uma instabilidade já basta para acumular requisições penduradas.

O ideal é que você sempre permita que o cliente envie um ID de rastreamento dele. Isso facilita correlacionar as requisições e os eventos da mesma operação e também permite criar pequenas ferramentas de diagnóstico: basta um endpoint para consultar o status da operação a partir desse ID. A API precisa persistir a associação entre o ID enviado pelo cliente e a ordem criada. Só colocar esse ID no log não resolve.

Além disso, esse ID pode servir como chave de idempotência, mas isso precisa fazer parte do contrato da API. Receber o ID e devolvê-lo no webhook não evita duplicações. Para isso, repetir a mesma operação com o mesmo ID precisa recuperar a operação existente, sem criar outra ordem, inclusive quando duas tentativas chegam ao mesmo tempo. A verificação e o registro precisam ser atômicos. Isso é idempotência, não debounce.

Defina o escopo de unicidade do ID, por exemplo, por cliente e tipo de operação, e por quanto tempo a API garante a deduplicação. Reutilizar o mesmo ID com dados diferentes deve dar erro. A documentação de idempotência da Stripe mostra um contrato com comparação de parâmetros e prazo de retenção. Na consulta por ID, valide também se a ordem pertence ao cliente autenticado; conhecer o ID não dá permissão para acessar a ordem.

Trabalha com tecnologia desde a década de 90. Já atuou na área de educação e participou de projetos de mobilidade de grande volume para laboratórios farmacêuticos. Criou games tanto para PC, como para iOS. Hoje está direcionando seus esforços em plataformas de Sistemas Embarcados, IoT, microservices e cloud computing. É um entusiasta de tecnologias como Golang e Docker.

Ver perfil