Files
baya-monorepo/dev/post-phase/server/runtime-services.md
T
2026-07-10 11:49:55 +03:30

18 KiB
Raw Blame History

Runtime services — deployment topology

Audit date: 2026-07-10 · Derivation: every entry below is justified from the code — the DI seam that depends on it, the config key that names it, or the package/startup wiring that talks to it. Nothing is invented; "not needed" claims are backed by the absence of the package/code. Make-it-real steps live in dev/shared-working-context/reports/mocks-registry.md (row references below).

The shape in one sentence: today the API binary talks to exactly one external system — SQL Server (app DB + log DB); everything else (18 seams) is an in-process mock, so "deployment" today is one container

  • one database — and the table below is the roadmap of what must exist as each seam goes real.

Service inventory

# Service Purpose Depends via (seam / config) MVP? Registry row
1 SQL Server (app DB Baya) System of record — 12 schemas (usr ops geo catalog verif search booking payments payouts reviews messaging partner) EF Core; ConnectionStrings:SqlServer Required now
2 SQL Server (log DB Baya_Logs) Serilog sink in deployed envs (Warning+, auto-created log.LogEvents) ConnectionStrings:logDb Required now (deployed)
3 Reverse proxy / TLS (nginx·caddy·traefik) TLS termination, HTTP/1.1+2, forwarded headers Kestrel config; JWE bearer Required now
4 Prometheus (+ Grafana) Scrapes /metrics; health forwarded to gauges UseMetricServer + OTel exporter Recommended now
5 Redis ICacheService + IDistributedLock (money-path mutex) Seams:* (keys TBD; none today) Before >1 API instance rows 14, 42
6 MinIO / S3 / ArvanCloud IObjectStorage — verification docs, avatars (REQ-006), invoice PDFs Seams:ObjectStorage:* Before real verification row 13
7 Job scheduler (Hangfire/Quartz, in-app on SQL) The deferred crons: payout batch, expiry scan, no-show, Moadian poll hosted services (no interface exists) Before unattended ops row 26
8 SMS gateway (Kavenegar·Ghasedak·SMS.ir) ISmsSender — OTP delivery (login is impossible without it) Seams:Sms:* (to be added) Launch-critical row 12
9 PSP / IPG + Shaparak (ZarinPal·Sadad·Vandar·Jibit) IPaymentProvider + IWebhookVerifier + ISettlementSplitProvider (تسهیم) encrypted payment_gateways.config_json Real payments rows 3941
10 BNPL providers (SnappPay·Digipay) IBnplProvider / IBnplProviderResolver / ICurrencyNormalizer Seams:Bnpl:*, Seams:Currency:*, gateway config Optional at launch rows 4647
11 Bank-transfer rail (Jibit·Vandar·Sadad payout API → PAYA/SATNA) IBankTransferProvider — weekly nurse payouts Seams:BankTransfer:* Real payouts row 23
12 سامانه مودیان (tax e-invoicing) IMoadianClient — legal invoice registration Seams:Moadian:* + signing cert Legal — soon after launch row 45
13 KYC bridge vendor (Finnotech-class) IShahkarVerifier + IIdentityKycProvider + IBankAccountOwnershipVerifier Seams:Shahkar:*, Seams:IdentityKyc:*, Seams:BankOwnership:* Real verification + payout gate rows 27, 28, 30
14 Geocoding (Neshan) IGeocoder — address → coordinates (EVV distance) Seams:Geocoding:* With real EVV row 31
15 Review-moderation classifier (LLM/API) IReviewModerationService — AI pre-screen Seams:ReviewModeration:* Optional (human queue is default) row 33
16 MoH / INO / eNamad ICredentialVerifier + ILicenseVerificationServicemanual admin process; no public B2B API exists Seams:LicenseVerification:* Manual = the MVP design rows 29, 50
17 Elasticsearch INurseSearch alt backend + outbox feeder Search:Backend (non-sql throws today) Not MVP — SQL search is real rows 38, 43

Explicitly not needed (verified absent from server/Directory.Packages.props and code): message broker (no RabbitMQ/Kafka), Redis today (no StackExchange.Redis), Hangfire/Quartz today, Elasticsearch client (Elastic.Clients.*), any cloud SDK, any payment/SMS vendor SDK. The only externally-pointing package beyond SQL Server is Serilog.Sinks.Elasticsearch — its wiring is commented out (Baya.Infrastructure.CrossCutting/Logging/LoggingConfiguration.cs:58-70).


Dependency graph

Solid edges are live today; dashed edges are behind a mocked seam (the arrow exists in code, the wire does not). One line per node below the graph.

flowchart LR
  subgraph fe[Frontend]
    WEB["Next.js client"]
  end
  subgraph host["API host — Baya.Web.Api (:5002)"]
    API["ASP.NET Core API\nREST /api/v1 · /metrics · /HealthCheck"]
    JOBS["In-proc interval jobs\n(→ Hangfire/Quartz later)"]
  end
  subgraph data[Data & platform infra]
    SQL[("SQL Server 'Baya'\n12 schemas · migrations on boot")]
    LOG[("SQL Server 'Baya_Logs'\nSerilog sink, Warning+")]
    REDIS[("Redis — cache + dist. lock\n(in-proc today)")]
    S3[("MinIO / S3 — object storage\n(local disk today)")]
  end
  subgraph money["Money rails (all mocked today)"]
    PSP["PSP / IPG + Shaparak\ncapture · webhook · تسهیم"]
    BNPL["BNPL — SnappPay / Digipay"]
    BANK["PAYA / SATNA payout rail"]
    MOAD["سامانه مودیان e-invoicing"]
  end
  subgraph trust["Trust & identity rails (all mocked today)"]
    SMS["SMS gateway (OTP)"]
    KYC["Shahkar · e-KYC · استعلام شبا"]
    GEOC["Neshan geocoding"]
    MODAI["Review-moderation classifier"]
    MANUAL["MoH / INO / eNamad\n(manual admin review)"]
  end
  subgraph obs[Observability]
    PROM["Prometheus (+ Grafana)"]
  end
  WEB -->|HTTPS/JSON| API
  API --> SQL
  API --> LOG
  JOBS --- API
  API -.-> REDIS
  API -.-> S3
  API -.-> SMS
  API <-.-> PSP
  API <-.-> BNPL
  API -.-> BANK
  API -.-> MOAD
  API -.-> KYC
  API -.-> GEOC
  API -.-> MODAI
  MANUAL -.- API
  PROM -->|scrape /metrics| API
  • Next.js client — the only API consumer; reads NEXT_PUBLIC_API_URL (root CLAUDE.md).
  • API — single ASP.NET Core host; all seams resolve in-process today.
  • In-proc jobs — the two BackgroundService sweeps (booking-request expiry 1 min, notification retention 24 h); the scheduler upgrade re-homes them (plan §4.1).
  • SQL Server Baya — system of record; migrations + seeding run on every non-Testing boot.
  • SQL Server Baya_Logs — deployed-env Serilog sink (auto-creates DB/table).
  • Redis — target for ICacheService/IDistributedLock; nothing speaks Redis yet.
  • MinIO/S3 — target for IObjectStorage; local disk + file:// URLs today.
  • PSP/IPG — card capture, callback signatures, تسهیم settlement split.
  • BNPL — provider-financed installments; settle/revert callbacks.
  • PAYA/SATNA — weekly nurse payout batches + async reconciliation.
  • مودیان — legal e-invoice registration (pending→registered poll).
  • KYC vendor — Shahkar phone↔NID, identity+liveness, Sheba ownership (payout gate).
  • Neshan — geocoding for address coordinates / EVV distance.
  • Moderation classifier — optional AI pre-screen; human moderation is the default gate.
  • MoH/INO/eNamad — human verification workflows, by design (no API exists).
  • Prometheus — scrapes /metrics; health check results forwarded as gauges.

Per-service notes

12 · SQL Server (Baya + Baya_Logs)

  • Evidence: UseSqlServer at server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:41; sink at …CrossCutting/Logging/LoggingConfiguration.cs:44-45 (schema log, auto-create); the 12 schemas via per-entity ToTable(name, schema) (e.g. PaymentsConfig/LedgerEntryConfig.cs:18, PayoutsConfig/NursePayoutConfig.cs:19).
  • Default: mcr.microsoft.com/mssql/server:2022-latest (Developer for dev; licensed edition in prod). Both DBs fit one instance; Baya_Logs can move later.
  • Config: ConnectionStrings:SqlServer, ConnectionStrings:logDbrotate + externalize first (plan §1.1; live sa credentials are committed today).
  • Health/readiness: the app's only health check (/HealthCheck, Monitoring/Configurations/HealthCheckConfigurations.cs:17); logDb has none (plan §7.2). Boot runs MigrateAsync + 3 seeders (Program.cs:99-104) → the login needs DDL rights and concurrent multi-node boot races (plan §4.3).

3 · Reverse proxy / TLS

  • Evidence of need: JWE bearer auth (RequireHttpsMetadata must be true in prod — Identity/ServiceConfiguration/ServiceCollectionExtension.cs:139); Kestrel EndpointDefaults=Http2 breaks non-TLS HTTP/1.1 (appsettings.json:29-33, plan §1.5); the rate limiter partitions on RemoteIpAddress with no ForwardedHeaders middleware (WebFramework/ServiceConfiguration/RateLimitingServiceExtension.cs:69, plan §1.6) — the proxy must pass X-Forwarded-For and the app must be taught to honor it.
  • Default: caddy 2 / nginx 1.27; terminate TLS, h2 to clients, HTTP/1.1 (or h2c) upstream once §1.5 lands.

4 · Prometheus (+ Grafana)

  • Evidence: /metrics via prometheus-net UseMetricServer + OTel AddPrometheusExporter (two stacks — consolidate, plan §7.1) at Monitoring/Configurations/PrometheusMetricsConfigurations.cs:11 and OpenTelemetryConfigurations.cs:21; health forwarded (HealthCheckConfigurations.cs:18).
  • Default: prom/prometheus:v2.53 + grafana/grafana:11. No tracing backend exists yet (metrics-only); an OTLP collector becomes relevant with plan §7.1.

5 · Redis

  • Evidence of the gap: MemoryCacheService and InProcessDistributedLock (CrossCutting/Seams/MemoryCacheService.cs:11, InProcessDistributedLock.cs:14) — single-process only; the money-path lock convention booking:{id}:payment|refund is already in the handlers.
  • Default: redis:7-alpine (AOF on). Required the moment a second API instance runs (shared cache invalidation generation-tokens + cross-instance money mutex). Config keys to be introduced with the swap (plan §4.2); none exist today.

6 · MinIO / S3 / ArvanCloud

  • Evidence: LocalDiskObjectStorage writes under a temp root and returns file:// URLs (CrossCutting/Seams/LocalDiskObjectStorage.cs:20,56); consumers: b6 verification documents (signed-URL upload flow), future avatars (REQ-006), invoice PdfStorageKey (InvoicesConfig/InvoiceConfig.cspdf_storage_key column).
  • Default: minio/minio:latest (S3-compatible; ArvanCloud object storage is the Iran-hosted option).
  • Config: Seams:ObjectStorage:RootPath today → bucket/endpoint/keys with the swap. Presigned PUT/GET with expiry is the contract the frontend already codes against.

7 · Job scheduler

  • Evidence: two PeriodicTimer hosted services only (Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:63,67); the payout/expiry/no-show/ Moadian crons are admin-manual with seeded-but-unread cadence keys (plan §4.1). No IJobScheduler interface exists — the registry name is aspirational.
  • Default: Hangfire on the existing SQL Server (no new container) — dashboard behind admin auth; or Quartz with SQL persistence. Not a separate service to "spin up", but it changes the SQL footprint (schema) and ops (dashboard, retries).

8 · SMS gateway

  • Evidence: LoggingSmsSender logs OTPs instead of sending (CrossCutting/Seams/LoggingSmsSender.cs:16) — no real user can log in; per-phone resend window + otp rate policy already enforced upstream.
  • Default: Kavenegar / Ghasedak / SMS.ir (SaaS — API key, no container). Keys to add: Seams:Sms:{ApiKey,SenderLine,BaseUrl} (registry row 12). Template/pattern OTP send for deliverability.

9 · PSP / IPG + Shaparak (تسهیم)

  • Evidence: MockPaymentProvider (instant success, VerifyAsync echoes the expected amount — CrossCutting/Seams/MockPaymentProvider.cs:24), MockWebhookVerifier (marker-based "signature" — MockWebhookVerifier.cs:21), MockSettlementSplitProvider (MockSettlementSplitProvider.cs:13). Real merchant credentials belong in the encrypted payment_gateways.config_json (seeded sandbox row: Persistence/ServiceCollectionExtensions.cs:109-117 — environment-gate it, plan §1.4).
  • Default: SaaS (ZarinPal / Sadad / Vandar / Jibit) — needs merchant + terminal registration and تسهیم (settlement-split) setup to registered IBANs; webhook endpoint is already public (POST webhooks/payments/{provider}) with upsert-first idempotency in place.

10 · BNPL providers

  • Evidence: MockBnplProvider drives the full verb set + state machine (CrossCutting/Seams/MockBnplProvider.cs:17-53); callback endpoint WebhooksBnplController (signed, rate-limited). Seams:Bnpl:* + Seams:Currency:TomanToIrrMultiplier (SeamOptions.cs:98-120,79).
  • Default: SnappPay and/or Digipay (SaaS, OAuth). Do plan §2.1 first — the revert-clearing path is currently unreachable, so real BNPL refunds would strand ledger state.

11 · Bank-transfer rail (PAYA/SATNA)

  • Evidence: MockBankTransferProvider settles every instruction instantly (CrossCutting/Seams/MockBankTransferProvider.cs:18-31); the handler already chooses PAYA vs SATNA by payout_satna_threshold_irr (platform_configs seed row 22) and the irreversibility backstops (unconditional UNIQUE(booking_id) link, forward-only payout machine) are in place.
  • Default: Jibit / Vandar / Sadad payout API (SaaS). Needs the source settlement account + the async submitted → paid/failed reconciliation callback the mock collapses (registry row 23, steps 34).

12 · سامانه مودیان

  • Evidence: MockMoadianClient leaves invoices pending forever (CrossCutting/Seams/MockMoadianClient.cs:21); no reconciliation job exists (plan §6.5). VAT-on-commission + sequential invoice numbers are already correct server-side.
  • Default: government SaaS — enrollment (memory/economic code) + a signing certificate; the certificate is a deploy-time secret.

13 · KYC bridge (Shahkar / e-KYC / استعلام شبا)

  • Evidence: three deterministic mocks with magic-value failure cases (MockShahkarVerifier.cs:26-37, MockIdentityKycProvider.cs:25, MockBankAccountOwnershipVerifier.cs:26); handlers already persist external_response_json and treat shared-SIM as a handled state. The Sheba-ownership result gates first payouts (matched_national_id).
  • Default: one Finnotech-class bridge covers all three استعلام‌ها (SaaS; API keys under Seams:{Shahkar,IdentityKyc,BankOwnership}:*).

14 · Geocoding (Neshan)

  • Evidence: MockGeocoder jitters ±5 km around 8 hardcoded centroids (MockGeocoder.cs:52) — EVV distance checks are noise until this (and/or REQ-008's user pin) is real. Seams:Geocoding:* is one of only three seam sections present in appsettings.json (:22-26).
  • Default: Neshan (Iran coverage; SaaS API key), rate-limit/retry per registry row 31.

15 · Review moderation

  • Evidence: keyword-list mock; clean text stays in the human queue by default (MockReviewModerationService.cs:23; Seams:ReviewModeration:*). Human ModerateReviewCommand retains decision authority — so this can stay mocked indefinitely at low volume.
  • Default: any text-moderation API / LLM endpoint when review volume outgrows the human queue.

16 · MoH / INO / eNamad — a process, not a service

  • Evidence: MockCredentialVerifier always returns RequiresManualReview (MockCredentialVerifier.cs:18); MockLicenseVerificationService likewise (MockLicenseVerificationService.cs:26). The registry itself records no public B2B API exists — the admin review queue is the real implementation. Provision: admin staffing + the f15 console.

17 · Elasticsearch — deliberately later

  • Evidence: SqlNurseSearch is the real MVP backend; Search:Backendsql throws at startup (Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:74-78); no ES client package.
  • Default when needed: elasticsearch:8.x + the outbox feeder (registry rows 38/43; plan §8.1).

Deployment notes (from the code, not aspiration)

  1. Boot = migrate + seed. Every non-Testing start applies EF migrations and seeds roles, the admin/qw123321 user, and an active sandbox ZarinPal gateway (Program.cs:99-104). Until plan §1.3/§1.4/§4.3 land: single-instance start-up, DDL-privileged login, and clean up the seeded credentials per environment.
  2. Environment files: appsettings.jsonappsettings.Development.json (byte-identical); no Production/Staging file exists. All non-secret env differences ride on ~14 Seams:* groups whose defaults live in code (SeamOptions.cs), not in config files.
  3. HTTP posture: HTTP/2-only Kestrel default (plan §1.5), gRPC plugin + reflection always on (plan §7.5), TLS required for JWE sanity.
  4. Single-instance constraints today: in-memory cache, in-proc money lock, in-proc sweeps, per-instance rate-limit buckets. Scaling past one instance requires plan §4.2 (Redis) + §4.3 (migrations) first — the DB uniques keep money correct either way, but locks/cache/limits silently degrade.
  5. Logs: deployed envs write Warning+ to Baya_Logs only (Information dropped — plan §7.3); dev writes console + logs/log.json.
  6. Client: the Next.js app needs NEXT_PUBLIC_API_URL pointing at the proxy; wire casing camelCase; snake_case routes.