Medyzen Backend Architecture
Version: 1.0 | Status: Verified against code 2026-09-16 | Owner: Engineering
Source of truth: medyzen-backend/cmd/server/main.go, medyzen-backend/internal/, medyzen-backend/deploy/aws/*.tf, .github/workflows/deploy-aws.yml. If this doc and the code disagree, the code wins — update this file.
Interactive version: https://claude.ai/artifact/7uUCnb5Ezre6BzDdR5QoNu
01_Overview
One Go service (modular monolith) behind api.medyzen.in serves every Medyzen app and panel. Modules are separated in code, not in deployment: one process, one database, one API.
- Region: AWS
ap-south-1(GCP is a frozen rollback, not live) - Runtime: ECS Fargate, ARM64, container port
8080 - Language / libs: Go, GORM (Postgres), golang-migrate, go-redis, razorpay-go, sentry-go, Prometheus client
02_Runtime Diagram
03_Request Path
Middleware, outermost first (cmd/server/main.go):
| # | Middleware | Job |
|---|---|---|
| 1 | SecurityHeaders | Standard security response headers |
| 2 | CORS | Explicit origin allow-list; wildcard refused at boot |
| 3 | RequestID | Correlation id per request |
| 4 | Sentry (observability) | Error capture |
| 5 | Recovery | Panic to 500 |
| 6 | Timeout | REQUEST_TIMEOUT |
| 7 | Logging | Structured logs to CloudWatch |
| 8 | Audit | Writes an audit row (all but health/metrics/pprof/swagger) |
| 9 | Metrics | Prometheus counters |
Per route: JWT auth, RBAC, rate limit (Redis-backed when RATE_LIMIT_BACKEND=redis).
Boot refuses to start on: weak JWT secret, weak TRUSTED_PROXY_SECRET, wildcard CORS, REDIS_URL set but unreachable, REDIS_URL set without OTP_PEPPER.
If
REDIS_URLis unset, OTPs and the token blacklist live in one task's memory — never run more than one task in that state.
04_Domain Modules
Every module under internal/ has the same shape:
| Folder | Role |
|---|---|
routes/ | Registers paths on the shared mux |
handlers/ | Decode, validate, respond |
services/ | Business rules, transactions |
repositories/ | GORM queries |
models/, dtos/ | Tables and wire shapes |
Modules: order, cart, prescription, inventory, procurement, finance, human (auth, users, roles), hr, assets, hospitalcatalog, dashboard, notification, leads, careers, hiring, deliverykyc, audit, realtime (order events over Redis pub/sub).
Shared code in internal/pkg: crypto, PII encryption/masking, SMS, mailer, storage (R2), push, genai (Gemini), turnstile, redisclient, worker supervisor, pagination, response.
05_Background Workers
Run in-process, supervised, drained on shutdown:
| Worker | Job |
|---|---|
order.reconciler | Reconcile payments with Razorpay |
finance.payout_scheduler | Partner payouts |
prescription.review_sweeper | Rx review timeouts |
prescription.auto_enqueue_sweeper | Queue Rx for extraction |
prescription.submission_reaper | Clean abandoned submissions |
prescription.stale_extraction_sweeper | Retry stuck extractions |
human.refresh_token_purge | Delete expired refresh tokens |
hiring.calendar_health_refresher | Only if Google Calendar configured |
careers.retention_sweeper | Only if CAREERS_RETENTION_SWEEP_ENABLED |
Plus two bounded pools started at module construction: Rx extraction and calendar sync.
06_Data & Storage
| Store | Used for | Notes |
|---|---|---|
| PostgreSQL (RDS) | All relational data | db.t4g.micro in prelaunch sizing; migrations via golang-migrate |
| Redis | OTP store, token blacklist, rate limits, realtime pub/sub | Not managed in Terraform — only REDIS_URL secret |
| Cloudflare R2 | Prescriptions, KYC, resumes, documents | API token expires 2026-11-30 |
| Secrets Manager | All runtime secrets injected as env | — |
07_Deploy Path
Push to main deploys production (.github/workflows/deploy-aws.yml, self-hosted runner on EC2):
- Guard + Go tests
- Build image, push to ECR
- Run migrations (one-off
dbtoolsECS task) - Preflight (
-preflight: flags, rows, buckets, vendors) - Roll ECS service
- Smoke test
api.medyzen.in
Migrations auto-run on deploy. Unsafe ones go in
ops/pending-migrations.
08_Constraints That Bite
- Single task. Prelaunch sizing (
prelaunch.tfvars): 1 task, 256 CPU / 512 MB. Scale back withterraform applywithout that var file (defaults: 2 tasks, 512 / 1024,db.t4g.medium). - Redis outside Terraform. Recreating infra from Terraform alone does not bring it back.
- R2 token expiry 2026-11-30. All object storage fails if not rotated.
- Network layout. ECS tasks run in public subnets with public IPs (no NAT gateway); RDS sits in private subnets.
- Realtime gaps. Only checkout, walk-in and generic status change publish events; payment confirm, handover, delivered and cancels do not.