Estudio de caso arquitectónico · laboratorio público

Procesamiento asíncrono confiable con fallas y evidencias explícitas.

dotnet-observability-lab es una reference architecture educativa que utilizo para hacer verificable una línea completa de razonamiento: problema → restricciones → atributos de calidad → decisiones → implementación → fallas → observabilidad → evidencias.

Esta página es una lectura de portafolio. La documentación técnica canónica permanece en el repositorio del laboratorio.

Problema

Aceptar una escritura ahora, consolidarla después y seguir siendo correcto cuando algo falla.

Una API recibe un valor y necesita aceptarlo de forma durable e idempotente. Otro límite de servicio debe consolidar ese valor de forma asíncrona, sin llamadas HTTP entre API y sin convertir una redelivery del broker en una duplicación de negocio.

La verdadera dificultad no está en el valor decimal. Está en el intervalo entre persistir, publicar, consumir, confirmar y recuperar, incluso cuando no es posible saber con certeza si la etapa anterior terminó.

Restricciones y atributos de calidad

Las decisiones comienzan por aquello que debe seguir siendo verdadero ante una falla.

01

Sin API-to-API

La ingesta y la consolidación son límites independientes. La lectura no debe depender de la disponibilidad de la API de escritura.

02

Entrega at-least-once

RabbitMQ puede realizar redelivery. Los duplicados forman parte del contrato y deben tratarse como un comportamiento normal.

03

Estado autoritativo durable

PostgreSQL protege la idempotencia, Outbox, Inbox y el agregado. Redis es solo un atajo best-effort.

04

Observabilidad portable

OpenTelemetry define traces, métricas y logs. Aspire compone y muestra el entorno local, pero no enruta el tráfico de negocio.

ConfiabilidadConsistenciaObservabilidadOperabilidadRecuperabilidadCapacidad de evolución

Decisiones y trade-offs

Los patrones aparecen como respuesta a modos de falla concretos.

Mensajería asíncrona en lugar de una llamada síncrona

RabbitMQ desacopla la disponibilidad y la latencia entre la ingesta y la consolidación.

Trade-off: consistencia eventual y mayor costo operativo.

ADR 0004 ↗

Transactional Outbox

El valor y el evento pendiente se persisten en la misma transacción; un worker lo publica después con confirmación del broker.

Trade-off: polling, retry, cuarentena y posibilidad de publicación repetida.

Implementación ↗

Inbox durable en el consumidor

La identidad del mensaje y la actualización del agregado se confirman en la misma transacción PostgreSQL.

Trade-off: cada mensaje único exige trabajo durable y una futura política de retención.

Implementación ↗

At-least-once + idempotencia

El diseño no promete exactly-once. Hace que la repetición sea segura para el efecto de negocio.

Trade-off: cada consumidor debe tratar explícitamente la identidad y la duplicación.

Contrato del evento ↗

PostgreSQL autoritativo, Redis opcional

El caché reduce el costo de la repetición HTTP, pero una caída, expiración o eviction no pueden alterar la corrección.

Trade-off: los misses y las fallas todavía pueden llegar a la base de datos.

ADR 0003 ↗

OpenTelemetry como estándar

El contexto W3C cruza el límite asíncrono; Aspire es una de las superficies locales de inspección.

Trade-off: la propagación y la instrumentación deben mantenerse explícitamente.

ADR 0005 ↗

Flujo nominal

El happy path existe, pero no es toda la arquitectura.

  1. POST /valuessolicitud idempotente
  2. PostgreSQLvalor + Outbox atómicos
  3. Outbox Workerclaim + publish confirmado
  4. RabbitMQentrega at-least-once
  5. Consolidation WorkerInbox + agregado atómicos
  6. GET /consolidatedlectura posterior e independiente

Ver flujo dinámico en LikeC4 ↗

Fallas relevantes

La propiedad interesante del diseño aparece cuando se rompe la ruta nominal.

Solicitud HTTP duplicada

La misma key + el mismo valor devuelve el recibo original; un uso conflictivo devuelve 409. La restricción única en PostgreSQL resuelve la carrera concurrente.

Mensaje entregado nuevamente

Inbox evita un segundo efecto de negocio. El mensaje puede consumirse más de una vez sin volver a incrementar el agregado.

Broker no disponible

La API todavía puede aceptar y persistir la escritura. Outbox mantiene el trabajo pendiente para publicarlo después.

Redis no disponible

El fast path desaparece, pero no la corrección. La decisión de idempotencia vuelve a PostgreSQL.

Publish confirmado, commit no concluido

El evento puede volver a publicarse con la misma identidad; el consumidor trata el duplicado de forma segura.

Falla en la consolidación

Inbox y el agregado hacen rollback juntos. No queda un marcador falso de procesamiento concluido.

Ver los seis escenarios reproducibles ↗

Observabilidad

El diagnóstico debe atravesar el mismo límite que atraviesa el negocio.

El contexto W3C iniciado en la solicitud se persiste junto con Outbox, el worker de publicación lo reinicia y se propaga mediante headers AMQP hasta el consumidor. Las métricas de baja cardinalidad diferencian aceptación, publicación, procesamiento y duplicación; los logs estructurados registran los resultados operativos relevantes.

El objetivo no es “tener dashboards”. Es poder explicar por qué una escritura todavía no apareció en el modelo de lectura y localizar el límite donde el flujo está detenido o repitiéndose.

Ver pruebas de traces y métricas ↗

Límites deliberados

Una reference architecture no es una receta de producción.

El laboratorio mantiene un único PostgreSQL local con bases de datos lógicas separadas, no define una plataforma de deployment de producción y no implementa en v1 una política completa de bounded retry/DLQ en el consumidor. Tampoco promete ordenamiento de eventos ni exactly-once.

Estos límites son parte de la evidencia: muestran qué se decidió, qué se simplificó con fines educativos y qué decisiones deberían reabrirse en un sistema real.

Explorar el repositorio completo ↗