Skip to content

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):

#MiddlewareJob
1SecurityHeadersStandard security response headers
2CORSExplicit origin allow-list; wildcard refused at boot
3RequestIDCorrelation id per request
4Sentry (observability)Error capture
5RecoveryPanic to 500
6TimeoutREQUEST_TIMEOUT
7LoggingStructured logs to CloudWatch
8AuditWrites an audit row (all but health/metrics/pprof/swagger)
9MetricsPrometheus 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_URL is 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:

FolderRole
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:

WorkerJob
order.reconcilerReconcile payments with Razorpay
finance.payout_schedulerPartner payouts
prescription.review_sweeperRx review timeouts
prescription.auto_enqueue_sweeperQueue Rx for extraction
prescription.submission_reaperClean abandoned submissions
prescription.stale_extraction_sweeperRetry stuck extractions
human.refresh_token_purgeDelete expired refresh tokens
hiring.calendar_health_refresherOnly if Google Calendar configured
careers.retention_sweeperOnly if CAREERS_RETENTION_SWEEP_ENABLED

Plus two bounded pools started at module construction: Rx extraction and calendar sync.


06_Data & Storage

StoreUsed forNotes
PostgreSQL (RDS)All relational datadb.t4g.micro in prelaunch sizing; migrations via golang-migrate
RedisOTP store, token blacklist, rate limits, realtime pub/subNot managed in Terraform — only REDIS_URL secret
Cloudflare R2Prescriptions, KYC, resumes, documentsAPI token expires 2026-11-30
Secrets ManagerAll runtime secrets injected as env

07_Deploy Path

Push to main deploys production (.github/workflows/deploy-aws.yml, self-hosted runner on EC2):

  1. Guard + Go tests
  2. Build image, push to ECR
  3. Run migrations (one-off dbtools ECS task)
  4. Preflight (-preflight: flags, rows, buckets, vendors)
  5. Roll ECS service
  6. 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 with terraform apply without 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.

Internal — written against the code, not the other way around.