Skip to main content

Why monolith — and why this infrastructure shape

This is the foundational architectural decision for KA26. It locks the system shape we run today, the reasoning behind it, and the explicit trigger conditions that would make us revisit. It exists so that 6 months from now, when someone asks "why aren't we on microservices like everyone else?" — there's a single, considered answer.

The decision

KA26 is a pragmatic monolith plus three native mobile apps:

  • Single Next.js 15 application — handles all consumer web, admin web, seller web, doctor web, delivery web, AND every backend API route. One codebase, one deploy, one Cloud Run service.
  • Three separate Expo / React Native projectsmobile/ (consumer), mobile-seller/, mobile-doctor/. Distinct package IDs, separate APK builds, zero cross-imports.
  • Single Postgres database — Cloud SQL school-db instance, ~85 Prisma models, all reads and writes go through one schema.
  • Single GCP regionus-central1. Cloud Run + Cloud SQL + GCS bucket + Cloud Build all live there.
  • Single repositorysidgk/ka26-marketplace contains the Next.js app, all three mobile apps, the Prisma schema, the test suites, and the deployment scripts.

There is intentionally no microservices split, no service mesh, no multi-region setup, no Kubernetes cluster, no event-sourcing layer, no separate "API gateway" service.

Why this is the right shape for KA26 today

1. We have one engineer

The single biggest factor. With one engineer (the founder) building everything, every architectural choice has to optimize for iteration speed and debugging ease, not theoretical scalability.

A microservices architecture for KA26 would:

  • 3× the deployment complexity (each service needs its own pipeline)
  • 5–10× the infrastructure cost (each service needs its own Cloud Run instance, DB connection pool, monitoring)
  • 3–5× slower iteration (cross-service coordination on every feature)
  • Add failure modes (network calls between services can fail; in-process function calls cannot)
  • Require DevOps expertise the team doesn't have time to acquire

A monolith means: change a backend route + the consumer screen that uses it in the same commit, push once, ship in 9 minutes via the existing CI/CD. No coordination overhead.

2. Our scale is small (and will stay small for a long time)

Gadag has ~50,000 households. Even at 100% adoption (the year-end goal), that's ~50,000 daily active users. Cloud Run + a single Postgres instance handles that comfortably.

For context:

  • Cloud Run autoscales to thousands of instances; we currently run with min-instances=0 and have never exceeded a handful of concurrent instances
  • Postgres on db-g1-small handles ~500 queries/sec sustained — far above what 50k DAU produces
  • Our entire monthly GCP bill is under $80 (April 2026)

The classic monolith failure modes — deployment bottlenecks, team coordination paralysis, scaling ceilings — only kick in at much larger scale. We are nowhere near them.

3. The boundaries that matter ARE enforced

A common (correct) critique of monoliths: "they tend to become tangled balls of mud where everything depends on everything." We've explicitly enforced the most important boundaries:

  • App-isolation rule: 4 separate auth contexts (consumer, seller, doctor, delivery). They never cross-import. Pinned by 103 tests in tests/app-isolation.test.ts. A consumer endpoint cannot accidentally use seller auth and vice versa.
  • Per-feature folders under src/app/api/: every domain (orders, offers, requests, reels, doctor, etc.) has its own route folder. Easy to find code, easy to refactor.
  • src/lib/ discipline: shared business logic centralized into named modules (negotiation.ts, ai-search.ts, track.ts, email.ts, push.ts). Single source of truth per domain.
  • Mobile apps are physically separate codebases: mobile/, mobile-seller/, mobile-doctor/ each with own package.json, own node_modules, own design system.

This gives us most of the modularity benefits of microservices (clear boundaries, single ownership, independent reasoning) without paying the distributed-systems tax.

4. The full set of features being built is broad

KA26 covers shop, ads, bidding, voice, reels, requests, orders, delivery, doctor (deprioritized), admin, and seller flows. A monolith makes cross-feature work trivial — when a bid converts to an order, no inter-service contract has to be negotiated; the same Prisma transaction handles both.

In a microservices world, the bidding service and the orders service would need a published contract, an event bus, idempotency tokens, retry semantics, and integration tests across both. That's the right cost when you have 50 engineers across 5 teams. It's an absurd cost when you have one.

Real-world precedents

Most engineering blogs glamorize microservices. The reality at well-known companies:

CompanyReality
StripeRan as a Ruby monolith for 5 years before extracting any services. Processed billions in payments on it.
ShopifyStill a Ruby monolith — one of the largest in the world. Serves ~10% of all e-commerce.
InstagramDjango monolith with a billion users. Only recently extracted some services.
Basecamp / HEYProudly monolithic Ruby. ~70 engineers. Famously productive.
GitHubRuby monolith for years. Still mostly is.
GoogleMassive monorepo (single repo, billions of lines). Service-oriented internally but a unified codebase.
AppleiOS frameworks are massive monoliths. Services side is microservices but each service is BIG, not the Netflix-style fine-grained kind.
Anthropic / OpenAIModular Python services with shared libraries. Not "microservices" in the Conway-sense.
BanksMostly legacy COBOL mainframes wrapped in REST/GraphQL. Microservices only for greenfield. Slower to ship than you'd think.

The pattern: successful companies start as monoliths and extract services only when forced to by team size or scale. The companies that started as microservices are mostly the ones that died from operational complexity before reaching product-market fit.

When we'd revisit

We will reconsider this architecture when any one of these triggers:

TriggerWhat it implies
5+ engineersMultiple teams need to deploy independently without stepping on each other
10,000+ DAU sustainedSingle Cloud Run instance starts hitting cold-start issues, Postgres write contention shows up on hot tables
Genuinely different scaling characteristicsE.g. video transcoding (CPU-intensive, batch) vs API serving (request-response, low CPU) — these benefit from different infra
Compliance isolation requirementE.g. healthcare data must run in a separate process per HIPAA-equivalent regulation
Per-feature SLO requirementsE.g. payments must have 99.99% uptime guarantees that other features don't need

We are at: 1 engineer, ~100 DAU, uniform load, no compliance separation, no SLO contracts. All five triggers are far away. Probably 12+ months out. The architecture is correct.

What about the cost question

April 2026 GCP bill: ~$74/month. Honest breakdown of probable composition:

ServiceEstimated monthly costWhat it does
Cloud SQL (Postgres)$30–50Always-on; biggest line item
Cloud Run$5–15Pay-per-request; scales to zero
Cloud Storage (GCS)$1–5Images, APKs, audio recordings
Cloud Build$5–10Per-deploy build minutes
Logging / Monitoring$1–5Mostly free tier
Networking egress$0–5Free first 1TB/month

This is genuinely cheap for what we run — a hobby app on Heroku costs $25/mo and gives 1/10 the infrastructure.

At 5,000 DAU we project ~$200/mo. At 50,000 DAU ~$1,500–3,000/mo. All reasonable. Cost is NOT a reason to refactor architecture today.

Cost optimization knobs (if it ever matters)

In rough order of impact:

  1. Cloud SQL is the biggest line. Could downsize to db-f1-micro (~$10) until traffic grows. Risk: tighter memory means more swap = worse query latency. Worth it if cost is acute, not for general practice.
  2. Cloud Run already runs min-instances=0 — pays nothing when idle. Can't optimize further without sacrificing cold-start latency.
  3. GCS lifecycle policy — already in place; auto-deletes build artifacts older than 7 days.
  4. Cloud Build → GitHub Actions for the Docker build. GitHub Actions is free for public repos, generous for private. Saves the Cloud Build minutes line item.
  5. Cloud SQL → CockroachDB Serverless — free tier handles low traffic. Loses some Postgres feature parity. Only worth it if cost truly hurts.

None of these are worth the engineering time today. Cost is fine.

What we explicitly DON'T have, and why

These are deliberate omissions, not oversights:

Thing we don't haveWhy
Microservices splitSee above — wrong tradeoff at our scale
KubernetesCloud Run handles autoscaling without operational complexity
Service mesh (Istio/Linkerd)Solves a problem we don't have (we have one service)
Multi-region active-activeSingle-region GCP outage = downtime, but the cost + complexity of multi-region is 10× the value at this scale
Event sourcing / CQRSAdds complexity for write-heavy systems. We're read-heavy.
GraphQLREST + Prisma is enough. GraphQL adds tooling cost (codegen, schemas, query complexity limits) that doesn't pay off until 10+ frontend consumers exist
Redis caching layerPostgres handles current load fine. Will add when first query starts to bottleneck.
Background job queueMost async work is fast enough to do inline. Cart-abandonment + similar use GitHub Actions cron. Will add BullMQ when first long-running job appears.
Staging environmentThis one IS a gap; planned for the week of 2026-05-04. Single biggest risk-reduction we'll do this month.
Per-user feature flagsWe have per-env flags (PAYMENTS_ONLINE_ENABLED, etc.) — works for global on/off. Will add per-user when we need 10% rollout testing.

Anti-patterns we've explicitly avoided

Bugs we've fixed by NOT building:

  • No premature service splits. Every "we should have a separate service for X" instinct has been deferred. Modules in src/lib/ give us 90% of the benefit at 5% of the cost.
  • No premature performance optimization. No caching, no read replicas, no query result memoization. Will add when first slow query is identified, not before.
  • No premature consistency layers. No Kafka, no event bus, no async messaging. Database transactions handle our consistency needs.
  • No premature abstraction layers. Routes call Prisma directly. Will add a service/repository layer if 5+ engineers ever need to share business logic (unlikely for 12+ months).

How this ties back to product velocity

The architecture exists to serve the product. KA26 is in pre-product-market-fit. The single most important property of the system is: how fast can the founder ship and iterate.

Pragmatic monolith optimizes for that. Every layer we don't have is a layer we don't have to debug, deploy, or document. Every premature abstraction we don't ship is engineering time spent on actual user-facing features.

The 30-day focus principle (set 2026-05-01): no new feature surfaces, deepen what exists, ship the killer "X" use-case. The architecture already supports this. We won't change it during this focus window.

Reading list — internal cross-references

TL;DR

We have a pragmatic monolith because we have one engineer, ~100 DAU, and a 12-month window before any of the standard "you need microservices now" triggers fire. It's the right shape for our stage. Every successful consumer marketplace started this way. The infrastructure costs ~$74/month and will scale linearly to maybe $2k/month at 50k DAU — all reasonable. We'll revisit when 5+ engineers join, when we hit 10k+ DAU, or when a feature genuinely needs different scaling characteristics from the rest. None of these is close. Architecture is correct. Stop debating it; ship the product.