5.0 KiB
5.0 KiB
OTel BI Forecast App
OpenTelemetry-instrumented BI platform with microservices, frontend OIDC login plus backend token validation, read-only MSSQL data warehouse access, and PostgreSQL persistence for writable app data.
Architecture
- Frontend: React + TypeScript (
frontend/) - Backend microservices (
backend/microservices/):api_gateway: public API, frontend JWT validation, internal token minting, routing/audit forwardingbi_query: read-only MSSQL warehouse queriesanalytics: forecasting, rankings, recommendationspersistence: PostgreSQL writes/reads for app data
- Data sources:
- MSSQL (
WorldWideImporters,AdventureWorks2022DWH) read-only only - PostgreSQL writable app store (
audit_logs,forecast_runs,ranking_runs,recommendation_runs)
- MSSQL (
- Observability: OTLP/HTTP to Grafana Alloy (
/v1/traces,/v1/metrics)
Authentication Model
- Frontend uses OIDC Authorization Code + PKCE.
api_gatewayvalidates frontend bearer JWT (iss,aud, signature, expiry, optional scopes) against configured JWKS.api_gatewaymints short-lived internal service tokens (x-internal-service-token) for service-to-service calls.- Internal services (
analytics,bi_query,persistence) require valid internal token on non-health endpoints and enforce issuer/type checks. - Combine with K8s network controls (ClusterIP, NetworkPolicy, mTLS/service mesh where available).
Frontend uses OIDC Authorization Code + PKCE with:
VITE_OIDC_ENABLED=trueVITE_OIDC_AUTHORITY=<issuer-base-url>VITE_OIDC_CLIENT_ID=<frontend-client-id>VITE_OIDC_REDIRECT_URI=<frontend-url>VITE_OIDC_POST_LOGOUT_REDIRECT_URI=<frontend-url>VITE_OIDC_SCOPE=openid profile email
Backend security env:
REQUIRE_FRONTEND_AUTH=trueFRONTEND_JWT_ISSUER_URL=<oidc-issuer>FRONTEND_JWT_JWKS_URL=<issuer-jwks-url>FRONTEND_JWT_AUDIENCE=<api-audience>FRONTEND_REQUIRED_SCOPES=<space-separated>INTERNAL_SERVICE_SHARED_SECRET=<strong-random-secret-at-least-32-bytes>INTERNAL_SERVICE_ALLOWED_ISSUERS=api-gatewayMSSQL_TRUST_SERVER_CERTIFICATE=falseandPOSTGRES_SSLMODE=requirefor production TLS validation
Local Run (Microservices)
cd backend
python -m venv .venv
source .venv/bin/activate
pip install -e .
cp .env.example .env
Run services in separate terminals:
uvicorn microservices.persistence.main:app --host 0.0.0.0 --port 8103 --reload
uvicorn microservices.bi_query.main:app --host 0.0.0.0 --port 8101 --reload
uvicorn microservices.analytics.main:app --host 0.0.0.0 --port 8102 --reload
uvicorn microservices.api_gateway.main:app --host 0.0.0.0 --port 8000 --reload
Frontend:
cd frontend
npm install
cp .env.example .env
npm run dev
Set:
VITE_API_BASE_URL=http://localhost:8000VITE_OTEL_COLLECTOR_ENDPOINT=http://alloy.monitoring.svc.cluster.local:4318
Frontend sends Authorization: Bearer <token> from the active OIDC session.
API Endpoints (via Gateway)
GET /api/healthGET /api/telemetry/statusGET /api/kpisGET /api/history?days_back=365GET /api/forecasts?days=30GET /api/rankings?top_n=10GET /api/recommendationsGET /api/dashboardGET /api/storage/audit-logs?limit=50GET /api/storage/forecasts?limit=50GET /api/storage/rankings?limit=50GET /api/storage/recommendations?limit=50
K8s Deployment
Example manifest:
k8s/microservices.yaml
It includes:
- namespace, config map, secret
- deployments/services for
api-gateway,bi-query,analytics,persistence - Alloy endpoint wiring via
OTEL_COLLECTOR_ENDPOINT - frontend JWT validation config and internal token secret wiring
- hardened pod security defaults (
runAsNonRoot, dropped capabilities,seccompProfile: RuntimeDefault, no auto-mounted service account token)
Read-Only Guarantee
- MSSQL connections enforce
ApplicationIntent=ReadOnly. - Warehouse query layer only accepts
SELECT/WITH. - Writable operations are isolated to PostgreSQL only.
- Use SQL Server account with
SELECTgrants only.
OTel Coverage
- Frontend:
- W3C trace/baggage propagation
- document-load, user-interaction, fetch, XHR instrumentation
- manual dashboard spans
- Backend services:
- FastAPI request spans
- HTTP client spans for service-to-service calls
- SQLAlchemy spans (MSSQL and PostgreSQL)
- manual analytics + persistence spans
- audit/snapshot persistence telemetry
Verification
- Call
GET /api/telemetry/statuswith a valid frontend bearer token. - Confirm response has non-null
trace_idandspan_id. - Trigger
GET /api/dashboard; then verify records inGET /api/storage/audit-logs. - In Grafana/Tempo, confirm trace path includes:
api-gatewayspananalyticsspanbi-queryMSSQL spanspersistencePostgreSQL spans
- Call internal service endpoint directly without
x-internal-service-tokenand verify it returns401.
Optional Tests
cd backend
source .venv/bin/activate
pip install -e .[dev]
pytest