Dependency Injection and Startup Lifecycle
Date: 2026-03-21 Status: Implemented
Overview
This document details the refactored approach to dependency injection, client initialization, and the FastAPI application lifecycle within the BacMR backend. The goal of this architecture is to ensure isolated test environments, deterministic startup/shutdown behavior, and lazy initialization of external clients without breaking legacy import patterns in the router layer.
Architecture
1. FastAPI Lifespan Context Manager
The backend no longer relies on @app.on_event("startup") and @app.on_event("shutdown"). We have migrated to the lifespan context manager in app/main.py. This ensures that all dependencies are strictly initialized before the application starts accepting HTTP requests, and safely torn down (like the APScheduler) when the server stops.
2. The AppServices Registry
We introduced an AppServices class within app/core/dependencies.py. It acts as a typed container for all global application services (e.g., Supabase client, OpenAI client, Pinecone pipelines).
These services are not instantiated when the module is imported. Instead, they are explicitly hydrated when setup_dependencies(app) is invoked during the lifespan startup phase.
3. The LazyServiceProxy
To maintain backward compatibility with dozens of FastAPI routers that previously directly imported service singletons like this:
We implemented theLazyServiceProxy. This proxy object masquerades as the real service during import-time. It intercepts any attribute access (__getattr__) or execution (__call__) and transparently routes it to the properly initialized instance sitting inside the AppServices registry.
If a router attempts to use a service before setup_dependencies() is run (such as during misconfigured test suites), the proxy immediately raises a RuntimeError describing the misconfiguration.
4. Test Environment Isolation
In tests/conftest.py, the setup_dependencies() function is called explicitly after the unittest.mock library has applied patches to the foundational clients (supabase.create_client and openai.OpenAI).
This guarantees that when the AppServices registry is populated, it is filled exclusively with mock clients. This completely eliminates the risk of test suites making real network calls due to early module imports.