Section 13

Technology Decisions

Every choice, with the alternative rejected and why. Several of these deliberately depart from the stack in the original brief.

Decisions at a glance

LayerChoiceRejectedReason
BackendPython 3.12 + FastAPINestJS, FastifyConnectors and AI are irreducibly Python; a second ecosystem doubles the hiring pool needed
DatabasePostgreSQL 16MongoDBNatural keys need enforcement; cause-list matching is a join
QueueRedis + ARQKafka, RabbitMQThousands of jobs/day, not millions/hour
Object storeCloudflare R2S3Zero egress fees — the product serves PDFs constantly
WebNext.js + Tailwind + shadcn/uiVue 2 (EOL)App and admin in one codebase
MobileFlutterReact NativeAlready built and working — no reason to churn
AuthOIDC + phone OTPEmail-firstIndian advocates are phone-first
PaymentsRazorpayStripeStripe's India support is materially weaker for UPI
ObservabilityOpenTelemetry + Grafana CloudSelf-hosted LGTMFree tier covers this scale; no ops burden
HostingHetzner + Docker ComposeAWS EKS3–5× cheaper; K8s needs an owner you don't have
CI/CDGitHub ActionsJenkins, CircleCIFree at this scale, no infrastructure
SearchPostgres FTS, then MeilisearchElasticsearchNot needed until case-law search ships

The three departures worth arguing about

1. Python everywhere, not NestJS departs from brief

NestJS is genuinely excellent and I would recommend it for a different product. Here it means two backend languages, because the ~70 connectors and the AI service cannot reasonably be anything but Python — the parsing and OCR ecosystem lives there, and the existing connector knowledge is written in it.

Two ecosystems means two dependency stories, two CI paths, two sets of idioms, and a hiring requirement for people fluent in both. At your team size that tax buys nothing. FastAPI gives you the typed, decorator-driven, OpenAPI-generating developer experience that makes NestJS attractive in the first place.

Revisit if: the API surface grows far beyond the connector fleet in complexity, or you hire a strong Node team.

2. No Kubernetes departs from brief

The brief asked for Kubernetes. I would not adopt it now. At ₹800/user/year, infrastructure that requires a dedicated operator is a direct tax on survival. Docker Compose across two or three Hetzner machines runs this workload for roughly €50–150/month, versus $300+/month for equivalent AWS capacity before egress.

The workload also does not have the property that makes K8s pay: bursty, unpredictable, multi-team scaling. As noted in System Architecture, poll load is known in advance — a function of tracked matters, on a schedule you control. That is the easiest capacity-planning problem there is.

Revisit when: you have someone whose actual job is running it, or you genuinely need multi-region. The design keeps this cheap — everything is a container behind a load balancer, so the migration is real but bounded.

3. Postgres over MongoDB departs from existing system

Covered in Domain Model. The short version: the old system ships a remove-duplicate-matters command, which is what a missing unique constraint looks like in production.

Deliberately deferred

Good technologies that would be premature. Named here so that deferring them is a decision rather than an oversight.

TechnologyAdopt when
Kafka / NATSRedis queue depth becomes a real bottleneck
Elasticsearch / OpenSearchCase-law search ships as a product
ClickHousePoll analytics outgrow Postgres — likely year two
gRPCInternal service count justifies it — currently one API
Multi-regionUsers outside India, or a regulatory requirement
Vector DBSemantic search over orders becomes a feature

Automation from day one

Each of these is cheap to add at the start and expensive to retrofit.

CI on every PR

Lint, type-check, test, connector golden files. GitHub Actions.

Secret scanning

gitleaks pre-commit and in CI. The old system's defining failure — make it structurally impossible.

Automated deploys

Merge to main deploys staging; tag deploys production. OIDC, no long-lived tokens.

Connector canaries

Scheduled live checks per court, alerting to a watched channel.

Dependency updates

Renovate, grouped weekly, auto-merged when CI is green.

Backup verification

Nightly dump plus a weekly restore test. An untested backup is not a backup.

Generated API clients

OpenAPI to TypeScript and Dart on every merge. No hand-written client drift.

Migrations in CI

Alembic, forward-only, applied automatically on deploy.

Developer experience targets

TargetMeasureEnabled by
Onboarding< 1 hourCompose stack, seeded DB, fixtures instead of live courts
CI feedback< 5 minPure parse() tests need no network
Add a court< 1 dayConnector interface + config registry
Deploy to stagingon mergeGitHub Actions + OIDC
Client driftzeroGenerated from OpenAPI

Conventions: trunk-based development with short-lived branches, Conventional Commits, squash merges, and release notes generated from commit history. Nothing exotic — the goal is that none of it requires explanation.