The decisions worth talking about
Every pattern in this codebase has a rationale. These are the answers to the “why” questions — context, decision, tradeoffs, and the code that resulted.
Feature-Sliced Design (lite)
The layered model and one-way dependency rules from FSD, without the full specification overhead.
Next.js server actions via next-safe-action
Composable middleware, schema validation, and a single catch boundary via next-safe-action.
Modular monolith over microservices
Service boundaries drawn too early are a bet on requirements you don't have yet.
Domain-Driven Design (lite)
A finance domain has real invariants worth modelling explicitly. A todo app wouldn't justify this.
CQRS with a typed Command Bus
Commands and queries are separate concerns. The bus makes dispatch type-safe without boilerplate.
CQRS read model
A separate read model is justified. A separate database is not, at least not yet.
In-process event bus
Domain events decouple what happened from what happens next. The interface hides the delivery mechanism.
Persist-first event dispatch with async processing
Every event hits Postgres before any handler runs. QStash handles async delivery.
Event handler ordering
Registration order is implicit coupling. It works because processing is sequential. An explicit event chain would make the dependency visible.
JWT authentication with refresh flow
Short-lived JWTs carry only userId. A session ID cookie enables token refresh without re-authentication.
TOTP-based multi-factor authentication
Two-step login with challenge tokens, aggregate-owned events, and service-layer signing. No auth logic in handlers.
Feature flags
Tier-gated, cache-aside, zero vendor. A database table and a Redis cache replace a SaaS subscription.
OpenTelemetry + Grafana Cloud
Open standards for instrumentation. The backend is a config swap, not a rewrite.
Two-tier health checks
A shallow probe keeps the orchestrator happy. A scheduled deep check catches silent dependency failures before users do.