Estudo de caso arquitetural · laboratório público

Processamento assíncrono confiável com falhas e evidências explícitas.

O dotnet-observability-lab é uma arquitetura de referência educacional que uso para tornar verificável uma linha completa de raciocínio: problema → restrições → atributos de qualidade → decisões → implementação → falhas → observabilidade → evidências.

Esta página é uma leitura de portfólio. A documentação técnica canônica permanece no repositório do laboratório.

Problema

Aceitar uma escrita agora, consolidá-la depois e continuar correto quando algo falha.

Uma API recebe um valor e precisa aceitá-lo de forma durável e idempotente. Em outro serviço, esse valor precisa ser consolidado de forma assíncrona, sem chamada HTTP entre APIs e sem transformar uma entrega repetida pelo broker em duplicação do efeito de negócio.

A dificuldade real não está no valor decimal. Está no intervalo entre persistir, publicar, consumir, confirmar e recuperar — inclusive quando não é possível saber com certeza se a etapa anterior terminou.

Restrições e atributos de qualidade

As decisões começam pelo que precisa continuar verdadeiro sob falha.

01

Sem chamadas HTTP entre APIs

Ingestão e consolidação são limites independentes. A leitura não deve depender da disponibilidade da API de escrita.

02

Entrega at-least-once

O RabbitMQ pode entregar a mesma mensagem novamente. Duplicatas fazem parte do contrato e precisam ser tratadas como comportamento normal.

03

Estado autoritativo durável

O PostgreSQL mantém os registros duráveis de idempotência, Outbox, Inbox e do agregado. O Redis é uma otimização, sem papel na garantia de correção.

04

Observabilidade portátil

OpenTelemetry define traces, métricas e logs. Aspire compõe e exibe o ambiente local, mas não roteia tráfego de negócio.

ConfiabilidadeConsistênciaObservabilidadeOperabilidadeRecuperabilidadeEvolutibilidade

Decisões e trade-offs

Padrões entram como resposta a modos de falha concretos.

Mensageria assíncrona em vez de chamada síncrona

RabbitMQ desacopla disponibilidade e latência entre ingestão e consolidação.

Trade-off: consistência eventual e maior custo operacional.

ADR 0004 ↗

Transactional Outbox

O valor e o evento pendente são persistidos na mesma transação; um worker publica depois com confirmação do broker.

Trade-off: consultas periódicas, novas tentativas, quarentena e possibilidade de publicação repetida.

Implementação ↗

Inbox durável no consumidor

Identidade da mensagem e atualização do agregado são confirmadas na mesma transação PostgreSQL.

Trade-off: toda mensagem única exige trabalho durável e política futura de retenção.

Implementação ↗

At-least-once + idempotência

O desenho não promete exactly-once. Ele permite repetir o processamento sem duplicar o efeito de negócio.

Trade-off: cada consumidor precisa tratar identidade e duplicação explicitamente.

Contrato do evento ↗

PostgreSQL autoritativo, Redis opcional

O cache reduz o custo de requisições HTTP repetidas, mas sua indisponibilidade, a expiração ou a remoção de entradas não podem comprometer a correção.

Trade-off: buscas sem resultado no cache e falhas ainda podem exigir acesso ao banco de dados.

ADR 0003 ↗

OpenTelemetry como padrão

Contexto W3C atravessa a fronteira assíncrona; Aspire é uma das superfícies locais de inspeção.

Trade-off: propagação e instrumentação precisam ser mantidas explicitamente.

ADR 0005 ↗

Fluxo nominal

O fluxo de sucesso existe, mas não representa a arquitetura inteira.

  1. POST /valuesrequisição idempotente
  2. PostgreSQLvalor + Outbox atômicos
  3. Outbox Workerclaim + publish confirmado
  4. RabbitMQentrega at-least-once
  5. Consolidation WorkerInbox + agregado atômicos
  6. GET /consolidatedleitura posterior e independente

Ver fluxo dinâmico no LikeC4 ↗

Falhas relevantes

A propriedade interessante do desenho aparece quando o caminho nominal quebra.

Requisição HTTP duplicada

A mesma chave com o mesmo valor retorna o recibo original; uso conflitante retorna 409. A restrição única no PostgreSQL resolve corrida concorrente.

Mensagem entregue novamente

A Inbox impede segundo efeito de negócio. A mensagem pode ser consumida mais de uma vez sem incrementar o agregado novamente.

Broker indisponível

A API ainda pode aceitar e persistir a escrita. O Outbox mantém trabalho pendente para publicação posterior.

Redis indisponível

O fast path desaparece, mas a correção não. A decisão de idempotência volta ao PostgreSQL.

Publish confirmado, commit não concluído

O evento pode ser republicado com a mesma identidade; o consumidor trata a duplicata de forma segura.

Falha na consolidação

A gravação na Inbox e a atualização do agregado são revertidas juntas. Não fica um marcador falso de processamento concluído.

Ver os seis cenários reproduzíveis ↗

Observabilidade

Diagnóstico precisa atravessar a mesma fronteira que o negócio atravessa.

O contexto W3C iniciado na requisição é persistido junto ao Outbox, reiniciado pelo worker de publicação e propagado por headers AMQP até o consumidor. Métricas de baixa cardinalidade diferenciam aceitação, publicação, processamento e duplicação; logs estruturados registram resultados operacionais relevantes.

O objetivo não é “ter dashboards”. É conseguir explicar por que uma escrita ainda não apareceu no modelo de leitura e localizar a fronteira onde o fluxo está parado ou repetindo.

Ver testes de traces e métricas ↗

Limites deliberados

Uma arquitetura de referência não é uma receita de produção.

O laboratório mantém um único PostgreSQL local com bancos lógicos separados, não define uma plataforma de implantação em produção e não implementa no v1 uma política completa de tentativas limitadas e fila de mensagens não processadas (DLQ) no consumidor. Também não promete ordenação de eventos nem exactly-once.

Esses limites são parte da evidência: mostram o que foi decidido, o que foi simplificado para fins educacionais e quais decisões precisariam ser reabertas em um sistema real.

Explorar o repositório completo ↗