Lewati ke konten utama

Planning Service — Architecture

Last refreshed: 2026-06-16. Source-of-truth lihat services/planning_service/ di codebase + memory project_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_id di 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 ke db_kesles_merchant + payment DB)
  • Master data merchant / device — milik merchant_core_api / inventory_service
  • Financial KPI dashboard (live data) — milik dashboard_api native 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:

MigDescription
v1/001_initial_schema.sqlSchema planning + tabel defaults_* + scenarios + scenario_inputs + scenario_outputs
v1/002_seed_defaults.sqlSeed default growth projection + break-even constants
v1/003_tenant_id_ready.sqlTambah 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_apiForward ke planning_serviceOperation
GET /api/dashboard/planning/scenariosGET /internal/scenarios?merchant_id=List skenario per merchant
POST /api/dashboard/planning/scenariosPOST /internal/scenariosCreate scenario
GET /api/dashboard/planning/scenarios/:idGET /internal/scenarios/:idGet detail
PUT /api/dashboard/planning/scenarios/:idPUT /internal/scenarios/:idUpdate
DELETE /api/dashboard/planning/scenarios/:idDELETE /internal/scenarios/:idSoft delete
POST /api/dashboard/planning/scenarios/:id/computePOST /internal/scenarios/:id/computeTrigger 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 (forward Authorization header ke core_api /dashboard/auth/me untuk validasi). Outbound CORE_API_INTERNAL_KEY + AUTH_SERVICE_INTERNAL_KEY dipakai planning_service saat memanggil core_api / auth_service.

Service Hardening

Mengikuti feedback_service_hardening_pattern.md (8-item baseline):

  • validateConfig fail-fast di main.go (POSTGRES_DSN + CORE_API_BASE_URL required)
  • ✅ Pass-through JWT auth (forward Authorization header ke core_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)
  • slog event taxonomy
  • ✅ pgx pool (25/5/5min)
  • ✅ pgx/v5 stdlib driver
  • APP_ENV=production eksplisit 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