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 reference architecture 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. Outro limite de serviço deve consolidar esse valor de forma assíncrona, sem chamada HTTP entre APIs e sem transformar uma redelivery do broker em duplicação 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 API-to-API

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

RabbitMQ pode redeliver. Duplicatas fazem parte do contrato e precisam ser tratadas como comportamento normal.

03

Estado autoritativo durável

PostgreSQL protege idempotência, Outbox, Inbox e agregado. Redis é apenas um atalho best-effort.

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

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

Trade-off: polling, retry, 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 torna repetição segura no efeito de negócio.

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

Contrato do evento ↗

PostgreSQL autoritativo, Redis opcional

Cache reduz custo de repetição HTTP, mas queda, expiração ou eviction não podem alterar a correção.

Trade-off: misses e falhas ainda podem tocar o banco.

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 happy path existe, mas não é 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

Mesmo key + 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

Inbox e agregado fazem rollback juntos. 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

Reference architecture não é receita de produção.

O laboratório mantém um único PostgreSQL local com bancos lógicos separados, não define plataforma de deploy de produção e não implementa no v1 uma política completa de bounded retry/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 ↗