Integração Correios para Rastreamento de Entregas: Arquitetura Escalável com NestJS e Kafka
Introdução Nos sistemas de e-commerce e da logística, rastrear o status de entregas é essencial para garantir transparência e agilidade no atendimento ao cliente. Automatizar esse processo com integração direta aos Correios é um desafio que envolve lidar com APIs externas, mapeamento de status inconsistentes e atualização em tempo real. Neste artigo, vamos apresentar uma abordagem completa para orquestração de rastreamento de pedidos usando NestJS, aplicando os princípios de Clean Architecture, testes unitários robustos e integração com mensageria (Kafka). Desenho da Solução A solução gira em torno do caso de uso TrackingOrderStatusService, responsável por: Consultar configurações de contas com rastreamento ativo; Buscar os pedidos com status finalizados que devem ser atualizados; Integrar com a API dos Correios via ProviderTrackingApiGateway; Filtrar eventos novos não registrados; Mapear os eventos para um modelo interno de status; Persistir os novos eventos no repositório; Emitir mensagens Kafka sobre mudanças de status; Registrar novos status desconhecidos para posterior mapeamento. Pontos de Complexidade 1. Identificação de Status A API dos Correios utiliza uma combinação de codigo e tipo para representar eventos. Essa combinação foi padronizada com o helper: export function buildPartnerStatusId(codigo: string, tipo: string): string { return `${codigo}${tipo}`; } Isso garante que qualquer ponto do sistema que precise identificar unicamente um status tenha uma forma unificada, reduzindo erros e facilitando o mapeamento. 2. Detecção de Eventos Novos Para evitar a reprocessamento de eventos já persistidos, usamos o método getNewHistories. Ele compara os eventos vindos dos Correios com os eventos já salvos no histórico: private getNewHistories(tracking: TrackingData[], eventos: CorreiosEventoDTO[]): CorreiosEventoDTO[] { return eventos.filter(evento => !this.historyExists(evento, tracking)); } E o historyExists compara tanto o código concatenado quanto a data: private historyExists(evento: CorreiosEventoDTO, tracking: TrackingData[]): boolean { return tracking.some(hist => { return ( hist.partner.statusCode === buildPartnerStatusId(evento.codigo, evento.tipo) && compareDates(hist.eventDate, evento.dtHrCriado) ); }); } 3. Fallback para Status Não Mapeados Se um evento dos Correios não tem status mapeado, salvamos esse status para posterior análise e notificamos por Kafka: if (!statusMapped) { const newStatusPartner = new StatusCodePartner({ statusId, status }); await this.statusCodePartnerRepository.save(newStatusPartner); await this.messageProvider.sendMessage({ topic: KAFKA_TOPIC_FREIGHT_STATUS_CODE_PARTNER_NOT_FOUND, messages: [{ key: JSON.stringify({ id: newStatusPartner.id }), value: { data: { ... } }, }], headers: { 'X-Tenant-Id': accountId, 'X-Correlation-Id': randomUUID() }, }); } 4. Resiliência e Logging Todas as etapas do processo possuem logging contextualizado: this.logger.log({ id: 'TrackingOrderStatusService.trackingOrders', message: `[${tracking.accountId}|${tracking.order.internalOrderId}] Evento processado com sucesso.`, }); Além disso, exceções são capturadas e logadas para facilitar troubleshooting: catch (error) { this.logger.error({ id: 'TrackingOrderStatusService.execute', message: error.message, stack: error.stack, }); } Abstração via API Gateway Outro destaque importante da implementação é o uso do ProviderTrackingApiGateway, uma interface que abstrai o consumo da API dos Correios. Ao invés de depender diretamente de chamadas HTTP acopladas no serviço, a integração é feita via uma porta definida por contrato: export interface ProviderTrackingApiGateway { getTracking(etiqueta: string, cep: string): Promise; } Essa abordagem segue o princípio da Inversão de Dependência (DIP), permitindo: Substituir facilmente a implementação por mocks em testes unitários: correiosApiGateway = mock(); correiosApiGateway.getTracking.mockResolvedValue(mockCorreiosTrackingResponse); Trocar facilmente a origem do rastreamento (ex: transportadoras privadas) sem alterar o caso de uso Reduzir acoplamento e facilitar manutenção, pois a lógica de orquestração não precisa conhecer detalhes de implementação HTTP, headers ou autenticação Essa separação clara entre camada de aplicação e infraestrutura melhora a testabilidade e prepara o código para cenários mais complexos. Boas Práticas Adotadas Inversão de dependência via tokens de injeção; Separacão de responsabilidades (cada provider tem uma única função); Logging com identificadores contextuais ([accountId|orderId]); Utilização de Promise.all para operações paralelas (status + configuração); Mensagens Kafka emitidas apenas para eventos novos. Testes Unitários

Introdução
Nos sistemas de e-commerce e da logística, rastrear o status de entregas é essencial para garantir transparência e agilidade no atendimento ao cliente. Automatizar esse processo com integração direta aos Correios é um desafio que envolve lidar com APIs externas, mapeamento de status inconsistentes e atualização em tempo real.
Neste artigo, vamos apresentar uma abordagem completa para orquestração de rastreamento de pedidos usando NestJS, aplicando os princípios de Clean Architecture, testes unitários robustos e integração com mensageria (Kafka).
Desenho da Solução
A solução gira em torno do caso de uso TrackingOrderStatusService
, responsável por:
- Consultar configurações de contas com rastreamento ativo;
- Buscar os pedidos com status finalizados que devem ser atualizados;
- Integrar com a API dos Correios via
ProviderTrackingApiGateway
; - Filtrar eventos novos não registrados;
- Mapear os eventos para um modelo interno de status;
- Persistir os novos eventos no repositório;
- Emitir mensagens Kafka sobre mudanças de status;
- Registrar novos status desconhecidos para posterior mapeamento.
Pontos de Complexidade
1. Identificação de Status
A API dos Correios utiliza uma combinação de codigo
e tipo
para representar eventos. Essa combinação foi padronizada com o helper:
export function buildPartnerStatusId(codigo: string, tipo: string): string {
return `${codigo}${tipo}`;
}
Isso garante que qualquer ponto do sistema que precise identificar unicamente um status tenha uma forma unificada, reduzindo erros e facilitando o mapeamento.
2. Detecção de Eventos Novos
Para evitar a reprocessamento de eventos já persistidos, usamos o método getNewHistories
. Ele compara os eventos vindos dos Correios com os eventos já salvos no histórico:
private getNewHistories(tracking: TrackingData[], eventos: CorreiosEventoDTO[]): CorreiosEventoDTO[] {
return eventos.filter(evento => !this.historyExists(evento, tracking));
}
E o historyExists
compara tanto o código concatenado quanto a data:
private historyExists(evento: CorreiosEventoDTO, tracking: TrackingData[]): boolean {
return tracking.some(hist => {
return (
hist.partner.statusCode === buildPartnerStatusId(evento.codigo, evento.tipo) &&
compareDates(hist.eventDate, evento.dtHrCriado)
);
});
}
3. Fallback para Status Não Mapeados
Se um evento dos Correios não tem status mapeado, salvamos esse status para posterior análise e notificamos por Kafka:
if (!statusMapped) {
const newStatusPartner = new StatusCodePartner({ statusId, status });
await this.statusCodePartnerRepository.save(newStatusPartner);
await this.messageProvider.sendMessage({
topic: KAFKA_TOPIC_FREIGHT_STATUS_CODE_PARTNER_NOT_FOUND,
messages: [{
key: JSON.stringify({ id: newStatusPartner.id }),
value: { data: { ... } },
}],
headers: { 'X-Tenant-Id': accountId, 'X-Correlation-Id': randomUUID() },
});
}
4. Resiliência e Logging
Todas as etapas do processo possuem logging contextualizado:
this.logger.log({
id: 'TrackingOrderStatusService.trackingOrders',
message: `[${tracking.accountId}|${tracking.order.internalOrderId}] Evento processado com sucesso.`,
});
Além disso, exceções são capturadas e logadas para facilitar troubleshooting:
catch (error) {
this.logger.error({
id: 'TrackingOrderStatusService.execute',
message: error.message,
stack: error.stack,
});
}
Abstração via API Gateway
Outro destaque importante da implementação é o uso do ProviderTrackingApiGateway
, uma interface que abstrai o consumo da API dos Correios. Ao invés de depender diretamente de chamadas HTTP acopladas no serviço, a integração é feita via uma porta definida por contrato:
export interface ProviderTrackingApiGateway {
getTracking(etiqueta: string, cep: string): Promise;
}
Essa abordagem segue o princípio da Inversão de Dependência (DIP), permitindo:
- Substituir facilmente a implementação por mocks em testes unitários:
correiosApiGateway = mock();
correiosApiGateway.getTracking.mockResolvedValue(mockCorreiosTrackingResponse);
Trocar facilmente a origem do rastreamento (ex: transportadoras privadas) sem alterar o caso de uso
Reduzir acoplamento e facilitar manutenção, pois a lógica de orquestração não precisa conhecer detalhes de implementação HTTP, headers ou autenticação
Essa separação clara entre camada de aplicação e infraestrutura melhora a testabilidade e prepara o código para cenários mais complexos.
Boas Práticas Adotadas
- Inversão de dependência via tokens de injeção;
- Separacão de responsabilidades (cada provider tem uma única função);
- Logging com identificadores contextuais (
[accountId|orderId]
); - Utilização de
Promise.all
para operações paralelas (status + configuração); - Mensagens Kafka emitidas apenas para eventos novos.