Connect Analyzer
Un dashboard de ventas en .NET 10 y Next.js construido como experimento de craftsmanship: la fuente de datos vive tras un puerto, así que pasar de un mock a SAP S/4HANA real o a Shopify es solo escribir un adaptador nuevo. El resto del sistema ni se entera.
El problema
Tengo que pintar un dashboard con datos de SAP. Hoy no hay SAP: hay un .txt que imita su export. Mañana habrá un sandbox de OData; pasado, una tienda real. ¿Cómo organizo el código para que ese baile no me obligue a reescribir el backend cada vez que cambia el origen?
La solución
-
Un puerto para el origen
La interfaz ISalesRepository es el único contrato que conoce de dónde vienen los datos. Todo lo demás depende del contrato, nunca de la implementación.
-
Tres adaptadores, una interfaz
Mock (.txt en Latin-1), SAP S/4HANA por OData y Shopify por Admin REST. Una variable de entorno (SalesSource) elige cuál se cablea en el composition root.
-
Persistencia tras un segundo puerto
La ingesta lee de la fuente y guarda en SQLite (ISalesStore); la analítica lee solo del almacén. Cambiar a Postgres sería otro adaptador, sin tocar el dominio.
-
Desplegado de verdad
Backend y mock en Google Cloud Run, frontend en Vercel, con deploy automático vía GitHub Actions y Workload Identity Federation (sin claves en secretos).
Stack técnico
Backend
- .NET 10
- C#
- Hexagonal
- Result/Error
- SQLite
- xUnit
Frontend
- Next.js
- TypeScript
- App Router
- Recharts
Infra & Deploy
- Docker
- Cloud Run
- Vercel
- GitHub Actions
- OData (SAP)
- Shopify
Decisiones técnicas
-
Origen de datos tras un puerto
Regla inviolable: ISalesRepository es lo único que conoce el origen. Añadir una fuente nueva = un adaptador outbound + cambiar su registro en Program.cs. El dominio nunca se entera.
-
Result/Error en vez de excepciones
Los errores esperables son valores (Result), no excepciones. El adaptador las captura en su borde; el controlador es el único punto que traduce Error → HTTP. Lo cuento en detalle en el post.
-
CI/CD sin claves
El deploy a Cloud Run se autentica con Workload Identity Federation: GitHub Actions obtiene credenciales efímeras de Google Cloud sin guardar ninguna clave de service account como secreto del repo.
Capturas
¿Cómo está montado por dentro?
Escribí un post largo sobre la arquitectura: puertos, adaptadores, Result/Error y testing sin librerías de mocking.