# Tech Stack Every choice here favours a **boring, well-understood monolith** over a distributed system. The goal was a platform one engineer can reason about end-to-end, that still scales to real traffic. ## Core | Layer | Choice | Why | |-------|--------|-----| | Framework | **Rails 8.1** | Mature, batteries-included, fast to build in | | Language | **Ruby** | Expressive; pairs naturally with Rails | | Database | **PostgreSQL** | Reliable, rich feature set, trigram search | | Web server | **Puma** | Multi-threaded, the Rails default | ## Front end The UI is server-rendered and progressively enhanced — no separate SPA. - **ERB views** rendered by Rails. - **Hotwire** — Turbo for partial page updates and streams, Stimulus for sprinkles of JavaScript. - **ViewComponent** — reusable, testable UI components under `app/components/`. - **Bootstrap 5** for styling. ## Background work - **Solid Queue** — database-backed jobs for transactional work (order confirmations, emails, OTPs). - **Sidekiq** — Redis-backed queue for high-volume real-time broadcasts. - **93 job classes** in total, covering notifications, reconciliation, campaign delivery, and scheduled maintenance. ## Auth & authorization - **Devise** for authentication, with OmniAuth for social login, plus magic-link and OTP flows. - **Pundit** policies for role-based authorization (customer, vendor, admin). ## Infrastructure - **Redis** — caching and rate-limit storage, deployed as a fail-open dependency. - **AWS SES** (API v2) for transactional and campaign email. - **Docker** for local development and production images. - **Kamal** for zero-downtime deploys. - **Prometheus** metrics and **Sentry** error tracking. ## Quality - **RSpec** with **factory_bot** for the test suite. - **Brakeman** for security scanning, **RuboCop** for style. - **rswag** generates an OpenAPI/Swagger spec for the public API. - **SimpleCov** for coverage reporting. ## Why a monolith A microservice split would have added network hops, distributed transactions, and operational overhead — for a system that one team can run comfortably as a single deployable. The monolith keeps refactors cheap and the mental model small. Where isolation matters (background jobs, the public API), it is drawn with module boundaries, not service boundaries.