Sense — Containers (L2)¶
The Sense slice of the platform-level container diagram, with notes on each box.
The full Platform Containers diagram covers all of Sense — this page reuses it but narrates only the Sense-relevant containers.
Frontend¶
Sense Mobile App¶
- Tech: Kotlin (Android) and Swift (iOS) native — no shared cross-platform framework.
- Storage: Local SQLite for offline queue (track session, pending frame uploads, cached detour tasks).
- Talks to: API Gateway via gRPC (HTTP/2). S3 directly for frame PUT.
- Auth: OIDC client; refresh tokens stored in keystore/keychain.
- Repo: separate mobile repos (not in scope of this site).
Planner Web App¶
- Tech: React 19, TypeScript 5.7, Vite 6, TanStack Router, TanStack Query, Zustand. MapLibre GL JS 5.18 + Deck.gl + PMTiles.
- API client: generated by Orval from the .NET Swagger document (
localhost:5501/swagger/v1/swagger.json). Generated hooks live undersrc/shared/api/generated/. Custom Axios mutator atsrc/shared/api/client.tsinjects the bearer token and silently refreshes on 401. - Architecture: Feature-Sliced Design —
app / pages / widgets / features / entities / shared. - Repo:
axion.sense.web.
Backend¶
Sense API¶
- Tech: .NET 10, ASP.NET Core. Two ports on one process: REST on 8080 (HTTP/1.1) and gRPC on 8081 (HTTP/2).
- Auth:
- OIDC JWT (primary) — RFC 7523, 5-minute clock skew tolerance.
- API key (
X-Api-Key) for service-to-service. - A policy scheme picks the right authenticator per request.
- Authz:
IPermissionServicewrapper around the OpenFGA C# SDK. Uses BatchCheck for list endpoints. - Migrations:
dotnet Axion.Sense.Api.dll --migrate(one-shot K8s job). - Repo:
axion.sense.backend/src/Axion.Sense.Api. - Components: Sense API L3.
Sense Worker¶
- Tech: .NET 10, KafkaFlow 3.1, Hangfire 1.x.
- Ports: 8080 (health, OIDC for Hangfire dashboard), 8888 (Hangfire Dashboard UI).
- Migrations:
dotnet Axion.Sense.Worker.dll --migrate— migratesaxion_sense_tasksand ensures Kafka topics. - Repo:
axion.sense.backend/src/Axion.Sense.Worker. - Components: Sense Worker L3.
Why two binaries (and not one)¶
The classic .NET-on-Postgres pattern is to host background workers inside the same process as the API, gated by an environment flag. We don't, because:
- Different scaling axes. API scales with concurrent users; Worker scales with Kafka throughput and Hangfire job pressure. Mixing them means over-provisioning one to feed the other.
- Different failure modes. A KafkaFlow consumer hot-loop or a long-running tippecanoe shell-out should never block API request threads.
- Different deployment cadences. Worker can roll independently to drain queues without affecting user-facing latency.
The price: a shared Axion.Sense.Core project with HostBinding markers so DI registrations target API or Worker correctly. Worth it.
Where to go next¶
- Sense API L3 — what's inside the API
- Sense Worker L3 — what's inside the Worker
- Data Model — Postgres + ClickHouse schemas
- Contracts — proto interfaces