Case study
recsbot
A recommendation product designed against managed cloud services that runs with none of them. Three adapter seams carry the difference, and one API contract hides it from the browser.
Constraint
Local by default, cloud by choice
Shape
A FastAPI backend stores conversations, preferences and feedback and streams recommendations over server-sent events. A Flask interface handles login and proxies that API to the browser. The supported default needs no account anywhere: SQLite on disk and a deterministic recommender.
The service binds to loopback. Exposing it on a network is a deliberate step with its own requirements: a strong authentication token, and a firewall or an authenticated reverse proxy in front. The database is treated as private data, because conversations and preferences are.
FastAPI, Flask, SQLite, server-sent events, Docker
Design
Three seams, one contract
Each seam picks an implementation at startup; the API surface above them does not change.
- Storage selects SQLite or DynamoDB.
- Chat selects a deterministic recommender, a local Ollama model, or Bedrock.
- Authentication selects an environment token or a managed secret.
- The frontend contract is the fixed point. Event names, message blocks, auth headers, conversation identifiers, cursor behaviour, feedback and preferences stay compatible, so swapping a seam never becomes a browser change.
- The deterministic recommender is the floor, not a stub. When a model is unreachable the product still answers, which is why the default install needs no model at all.
Replacement
What each managed service became
The cloud path remains optional reference functionality: tests and daily development never require it.
- Lambda behind a function URL became FastAPI under Uvicorn, or the Docker service.
- DynamoDB became SQLite behind the same storage interface.
- Bedrock became the deterministic recommender, or a local Ollama model.
- Secrets Manager became the process environment, or a local env file readable only by its owner.
- CloudWatch became the logs the host already keeps.
- A signing proxy on EC2 disappeared: the interface talks to the local backend directly.
Rules
How it behaves when something is missing
- Absent integrations degrade, they do not crash. Steam and Jellyfin are optional; without credentials the taste profile is empty and the application still starts. A missing optional dependency is a smaller profile, never a failed boot.
- Certificate verification stays on. A global switch to disable it would make every environment work and one of them unsafe; where a private certificate authority is needed, it gets installed instead.
- Secrets are generated, not shipped. A bootstrap script writes them into an ignored local file with owner-only permissions.
Boundary
What it deliberately is not
Scope
It is a personal, single-operator product, not a multi-tenant service. There are no adoption, latency or accuracy figures on this page because the project has not been run at a scale that would produce honest ones.