Seis meses después de diseñar un sistema, alguien te pregunta: “¿por qué usaste X en lugar de Y?”. Si documentaste la decisión, respondes en 30 segundos. Si no la documentaste, tienes que reconstruir mentalmente el contexto, las alternativas que consideraste, las restricciones que existían en ese momento, y defender una decisión que en ese momento tenía todo el sentido del mundo pero que ahora parece arbitraria.
Los ADRs (Architecture Decision Records) son la solución a ese problema. No son documentación por documentación — son registros de razonamiento. El por qué tomé esta decisión, qué alternativas consideré, y qué consecuencias acepto.
Documenté 14 ADRs en una plataforma de observabilidad. Este post explica el formato que usé y las decisiones que más valió la pena registrar.
El formato MADR
MADR (Markdown Any Decision Record) es una variante del ADR original de Michael Nygard, optimizada para legibilidad y consistencia. La estructura:
# ADR-NNNN: Título de la decisión
## Estado
Aceptado | Reemplazado por ADR-NNNN | En revisión
## Contexto
¿Cuál es el problema que motiva esta decisión?
¿Qué restricciones existen?
## Opciones consideradas
- Opción A
- Opción B
- Opción C (elegida)
## Decisión
Elegimos **Opción C** porque [razón].
## Consecuencias
**Positivas:**
- ...
**Negativas:**
- ...
## Referencias
- Link a RFC, ticket, benchmark, o documentación relevante
La sección “Opciones consideradas” es la más importante y la que más se omite. Sin ella, el ADR solo registra qué decidiste, no por qué no elegiste las alternativas.
ADR-0003: Sin collector en producción
Este ADR registra una decisión que parece obvia en retrospectiva pero que tuvo debate real.
# ADR-0003: Sin OpenTelemetry Collector en producción
## Estado
Aceptado
## Contexto
OpenTelemetry tiene dos modos de exportación:
1. Export directo: servicio → exporter → destino (Azure Monitor)
2. Via collector: servicio → OTEL Collector → exporter → destino
El collector agrega buffering, batching, y la capacidad de cambiar el destino
sin modificar los servicios. La arquitectura de referencia de OTel lo recomienda.
## Opciones consideradas
- **Con collector (sidecar):** mayor flexibilidad, requiere operar el collector
- **Con collector (daemonset):** solo viable en Kubernetes
- **Export directo (elegida):** menos componentes, menos complejidad operacional
## Decisión
Export directo a Azure Monitor. Sin collector.
## Consecuencias
Positivas:
- Sin componente adicional que operar y monitorear
- Sin single point of failure del collector
- Configuración más simple por servicio
Negativas:
- Acoplamiento al exporter de Azure Monitor en el SDK
- Si migramos de Azure Monitor, hay que actualizar el SDK
- Sin buffering del collector (mitigado: el exporter tiene retry interno)
## Referencias
- OpenTelemetry Collector documentation
- ADR-0009: SDK no crashea app (relacionado — si el exporter falla, el SDK falla silencioso)
El valor de este ADR: cuando alguien pregunta “¿por qué no tienen collector?”, la respuesta no es “no sé, así estaba cuando llegué”. Es “ADR-0003 — decisión consciente por complejidad operacional”.
ADR-0009: SDK no puede crashear la aplicación
# ADR-0009: SDK de observabilidad con defensive design
## Estado
Aceptado
## Contexto
El SDK de observabilidad es una dependencia transversal importada en múltiples
servicios productivos. Un error de inicialización (connection string inválido,
versión incompatible, problema de red) no debe propagarse como excepción al
servicio que lo importa.
## Opciones consideradas
- **Propagación de excepciones (fail-fast):** el servicio sabe inmediatamente
que la observabilidad no está configurada. Riesgo: crashea servicios productivos.
- **Defensive design con no-op fallback (elegida):** si el SDK falla al
inicializarse, continúa en modo no-op. El servicio sigue funcionando sin
observabilidad en lugar de no funcionar.
## Decisión
Defensive design. SDK nunca propaga excepciones al caller.
Fallo de inicialización → warning log + modo no-op.
## Consecuencias
Positivas:
- Los servicios no crashean por problemas de configuración del SDK
- El SDK puede actualizarse de forma más agresiva (bugs de inicialización no afectan uptime)
Negativas:
- Un servicio puede estar corriendo sin observabilidad sin saberlo
- Mitigación: health check explícito del SDK + alerta si modo no-op activo
## Referencias
- ADR-0002: Azure Application Insights como backend
- Patrón Null Object (GoF)
ADR-0011: Head-based sampling al 10%
# ADR-0011: Sampling de traces al 10% head-based
## Estado
Aceptado
## Contexto
Sin sampling, cada request genera una traza completa. Con tráfico de producción,
esto genera volumen de datos ingestados que impacta directamente el costo de
Azure Monitor (precio por GB ingestado).
## Opciones consideradas
- **Sin sampling (100%):** máxima visibilidad, costo alto
- **Tail-based sampling:** samplea basándose en el resultado (ej: solo errores).
Requiere collector para ver el trace completo antes de decidir. Incompatible
con ADR-0003 (sin collector).
- **Head-based 10% (elegida):** decisión al inicio del trace. Compatible con
export directo. Simple de implementar.
- **Head-based adaptativo:** ajusta el rate según el tráfico real. Más complejo,
no justificado para el volumen actual.
## Decisión
Head-based sampling al 10% con `TraceIdRatioBased(0.1)`.
## Consecuencias
Positivas:
- Reducción de ~90% en volumen de traces y costo asociado
- Compatible con export directo (ADR-0003)
Negativas:
- El 90% de los traces normales se pierden
- Mitigación: traces con error se pueden priorizar vía ParentBasedSampler
- Los casos de debugging ad-hoc pueden requerir aumentar el rate temporalmente
## Referencias
- OpenTelemetry Sampling documentation
- Azure Monitor pricing (ingestion)
ADR-0013/0014: Bicep como IaC principal, workspace centralizado
Algunos ADRs reemplazan a otros. ADR-0007 estableció Terraform como IaC. ADR-0013 documentó la migración a Bicep.
# ADR-0013: Reemplazar Terraform por Bicep como IaC principal
## Estado
Reemplaza ADR-0007
## Contexto
ADR-0007 estableció Terraform como IaC. En práctica, Terraform generó fricción:
- Plan/apply cycle agrega complejidad en pipelines CD
- El state file requiere backend remoto (storage account) con locking
- Los recursos Azure-específicos tienen mejor soporte nativo en Bicep
## Decisión
Bicep como IaC principal para recursos Azure. Terraform descontinuado.
## Consecuencias
Positivas:
- Sintaxis más simple para recursos Azure-nativos
- Sin state file que gestionar
- Integración nativa con Azure CLI y GitHub Actions
Negativas:
- Solo Azure (no portable a otros clouds)
- El equipo tiene que aprender Bicep
Cómo organizar los ADRs
Los ADRs viven en el repositorio junto al código. No en Confluence, no en Notion, no en un Google Doc separado. En el repo.
docs/
└── decisions/
├── ADR-0001-use-opentelemetry.md
├── ADR-0002-azure-application-insights.md
├── ADR-0003-no-collector-in-production.md
├── ...
└── ADR-0014-workspace-centralizado.md
El historial de git de un ADR es su historia: cuándo se tomó la decisión, qué cambió después, qué discusiones hubo en el PR. Un ADR en un doc de Notion no tiene eso.
Lo que aprendí
Los ADRs más valiosos son los que documentan por qué NO elegiste la opción obvia. “Por qué no Terraform”, “por qué no collector”, “por qué no 100% sampling” — esas son las preguntas que la gente hace. Los ADRs que registran “hice lo estándar porque es lo estándar” tienen menos valor.
Un ADR reemplazado no es un fracaso. Es evidencia de que el sistema evolucionó con una razón documentada. ADR-0007 (Terraform) no fue un error — fue la decisión correcta en ese momento con la información que había. ADR-0013 (Bicep) registra por qué cambió.
14 ADRs en un proyecto no es burocracia. Es el costo de 14 conversaciones de 30 minutos bien documentadas en lugar de 14 conversaciones de 2 horas cada vez que alguien nuevo entra al proyecto o alguien quiere cambiar algo.
Empieza con los ADRs más controvertidos. Si una decisión no generó debate, probablemente no necesita ADR. Si generó debate, necesita ADR urgentemente.