ADR-001: Use FastAPI for the API Layer
Status
Accepted (reflects existing implementation — backend/src/api/server.py)
Context
MarketCompass needs an API layer that: supports async I/O (LLM calls, external financial APIs are all network-bound), supports streaming responses (SSE for token-by-token chat output, per backend/src/streaming/), generates typed request/response schemas for a portfolio-quality developer experience (Swagger/OpenAPI), and integrates cleanly with pydantic/pydantic-settings, which the project already uses for configuration (config/settings.py).
Decision
Use FastAPI as the API framework, with route modules per resource (chat.py, agents.py, health.py, providers.py) and a single app factory (server.py).
Alternatives Considered
| Option | Rejected because |
|---|---|
| Flask | No native async support without extensions; no built-in request/response schema validation; would require bolting on Pydantic manually |
| Django REST Framework | Heavyweight for an API-only service with no ORM-centric data model in Phase 1; slower iteration for a portfolio timeline |
| Raw ASGI (Starlette only) | FastAPI is Starlette plus schema validation and docs generation — using raw Starlette would mean re-implementing what FastAPI already provides for free |
Consequences
Positive:
- Automatic OpenAPI docs at
/docs(already working perREADME_BACKEND.md) — valuable for interview demos. - Native
async defroute handlers match the I/O-bound nature of every downstream Tool call. - Pydantic models double as both request/response validation and the internal
RoutingDecision/SkillResult/ToolResultcontracts described in the architecture docs, keeping one schema philosophy end-to-end.
Negative / accepted tradeoffs:
- FastAPI's dependency-injection system is not used for Skill/Tool registry resolution in Phase 1 (the Orchestrator resolves registries directly) to avoid coupling core orchestration logic to a web-framework-specific DI mechanism — this preserves the "avoid framework lock-in" principle from
project-context.md. FastAPI's DI is used only for request-scoped API concerns (e.g. auth, request ID).
Interview Talking Point
FastAPI was chosen for I/O concurrency and schema-first design, but orchestration logic (Router/Skills/Tools) deliberately does not depend on FastAPI at all — it could be mounted behind Flask, a CLI, or a message queue consumer without modification. That separation is itself a design decision, not an accident.