Planning Service — Architecture
Last refreshed: 2026-06-16. Source-of-truth lihat
services/planning_service/di codebase + memoryproject_planning_service.md.
Service Boundary
planning_service owns
- Skenario "Growth Projection" — input device, output projected revenue + setup cost
- Skenario "Break-even" — perhitungan kapan ROI tercapai
- Skenario "Pricing Sensitivity" — what-if pricing model
- Planning scenarios CRUD per merchant (multi-tenant ready via
tenant_iddi DB) - Default scenario constants (
planning.defaults_growth_projection,planning.defaults_break_even, dll)
planning_service does NOT own
- Reports historis (revenue actual, transaction history) — milik
dashboard_api(native query kedb_kesles_merchant+ payment DB) - Master data merchant / device — milik
merchant_core_api/inventory_service - Financial KPI dashboard (live data) — milik
dashboard_apinative panel
Data Flow
apps/merchant_dashboard (Flutter Web, operator Kesles)
│
│ HTTPS (JWT dashboard session)
▼
services/dashboard_api (:8082)
│
│ HTTP (via PLANNING_SERVICE_BASE_URL=http://127.0.0.1:8097)
│ planning_service_proxy.go — 6 route
▼
services/planning_service (:8097)
│
▼
db_kesles_merchant_planning
planning.defaults_*
planning.scenarios
planning.scenario_inputs
planning.scenario_outputs
Nginx Upstream
VM nginx forward https://api-merchant.kesles.com/merchant/planning-api/* → http://localhost:8097/* (auth handshake via JWT-aware proxy di dashboard_api, bukan langsung dari client).
Database Schema
DB: db_kesles_merchant_planning
Migrations applied:
| Mig | Description |
|---|---|
v1/001_initial_schema.sql | Schema planning + tabel defaults_* + scenarios + scenario_inputs + scenario_outputs |
v1/002_seed_defaults.sql | Seed default growth projection + break-even constants |
v1/003_tenant_id_ready.sql | Tambah tenant_id UUID NULL ke semua tabel — SaaS-ready (Phase 2) |
Legacy schemas dihapus di main DB via db_kesles_merchant/migrations/v1/106_drop_legacy_planning_schemas.sql:
DROP SCHEMA IF EXISTS report CASCADE(old Growth Projection storage)DROP SCHEMA IF EXISTS planning CASCADE(legacy planning schema di main DB)
HTTP Endpoint Contract (dashboard_api → planning_service)
Semua endpoint internal via X-Internal-API-Key. JWT validation dilakukan dashboard_api di proxy layer.
| Endpoint dashboard_api | Forward ke planning_service | Operation |
|---|---|---|
GET /api/dashboard/planning/scenarios | GET /internal/scenarios?merchant_id= | List skenario per merchant |
POST /api/dashboard/planning/scenarios | POST /internal/scenarios | Create scenario |
GET /api/dashboard/planning/scenarios/:id | GET /internal/scenarios/:id | Get detail |
PUT /api/dashboard/planning/scenarios/:id | PUT /internal/scenarios/:id | Update |
DELETE /api/dashboard/planning/scenarios/:id | DELETE /internal/scenarios/:id | Soft delete |
POST /api/dashboard/planning/scenarios/:id/compute | POST /internal/scenarios/:id/compute | Trigger recompute |
Environment Variables
services/planning_service/.env.production
APP_PORT=8097
APP_ENV=production
POSTGRES_DSN=postgres://...@/db_kesles_merchant_planning
# Outbound ke core_api untuk validasi JWT dashboard session (forward Authorization header)
CORE_API_BASE_URL=http://127.0.0.1:8080
CORE_API_INTERNAL_KEY=<rotation per 90 hari>
# Outbound ke auth_service untuk enrich owner display name + email pada response scenario
AUTH_SERVICE_BASE_URL=http://127.0.0.1:8081
AUTH_SERVICE_INTERNAL_KEY=<rotation per 90 hari>
LOG_LEVEL=info
services/dashboard_api/.env.production (caller side)
# Aktifkan HTTP proxy ke planning_service; kosong = fallback handler lokal
PLANNING_SERVICE_BASE_URL=http://127.0.0.1:8097
# Read path ke db_kesles_merchant_planning (dual-write target)
PLANNING_POSTGRES_DSN=postgres://...@/db_kesles_merchant_planning
Auth model: planning_service tidak punya inbound
INTERNAL_API_KEY— auth pakai pass-through JWT dari dashboard session (forwardAuthorizationheader kecore_api /dashboard/auth/meuntuk validasi). OutboundCORE_API_INTERNAL_KEY+AUTH_SERVICE_INTERNAL_KEYdipakai planning_service saat memanggil core_api / auth_service.
Service Hardening
Mengikuti feedback_service_hardening_pattern.md (8-item baseline):
- ✅
validateConfigfail-fast di main.go (POSTGRES_DSN+CORE_API_BASE_URLrequired) - ✅ Pass-through JWT auth (forward
Authorizationheader kecore_api /dashboard/auth/me) — planning_service tidak punya inbound API key, jadi tidak ada API key compare di sisi planning_service - ✅ HTTP timeout (30s read/write)
- ✅ Graceful shutdown (SIGTERM handler)
- ✅
slogevent taxonomy - ✅ pgx pool (25/5/5min)
- ✅ pgx/v5 stdlib driver
- ✅
APP_ENV=productioneksplisit di .env line 1
Tenant Readiness (Phase 2 prep)
Semua tabel planning.* punya kolom tenant_id UUID NULL. Saat Phase 2:
- Tambah
tenant_id NOT NULL+ index per query - Authentication shift ke tenant-aware JWT (vs single-tenant API key sekarang)
- DB akan di-promote ke standalone instance (vs subscribe ke shared Postgres saat ini)
Cross-References
- Memory:
project_planning_service.md - Plan: Extraction Plan
- Status terkini: Service Status