Connect Analyzer
A sales dashboard in .NET 10 and Next.js built as a craftsmanship experiment: the data source lives behind a port, so going from a mock to real SAP S/4HANA or Shopify is just writing a new adapter. The rest of the system never notices.
The problem
I need to render a dashboard with SAP data. Today there is no SAP: there is a .txt mimicking its export. Tomorrow an OData sandbox; the day after, a real store. How do I structure the code so that this dance does not force me to rewrite the backend every time the source changes?
The solution
-
One port for the source
The ISalesRepository interface is the only contract that knows where data comes from. Everything else depends on the contract, never on the implementation.
-
Three adapters, one interface
Mock (.txt in Latin-1), SAP S/4HANA over OData, and Shopify over the Admin REST API. One env var (SalesSource) picks which one is wired at the composition root.
-
Persistence behind a second port
Ingestion reads from the source and saves into SQLite (ISalesStore); analytics read only from the store. Moving to Postgres would be another adapter, without touching the domain.
-
Actually deployed
Backend and mock on Google Cloud Run, frontend on Vercel, with automatic deploys via GitHub Actions and Workload Identity Federation (no keys stored as secrets).
Tech stack
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
Technical decisions
-
Data source behind a port
An inviolable rule: ISalesRepository is the only thing that knows the source. Adding a new one = an outbound adapter + swapping its registration in Program.cs. The domain never finds out.
-
Result/Error instead of exceptions
Expected errors are values (Result), not exceptions. The adapter catches them at its edge; the controller is the only place that translates Error → HTTP. I cover it in detail in the post.
-
CI/CD without keys
The Cloud Run deploy authenticates with Workload Identity Federation: GitHub Actions gets ephemeral Google Cloud credentials without storing any service-account key as a repo secret.
Screenshots
How is it built inside?
I wrote a long post about the architecture: ports, adapters, Result/Error and testing without mocking libraries.