Phase 1 — Dynamic Routing Infrastructure #106
Labels
No labels
architecture
backend
bug
ci
config
database
docker
documentation
duplicate
enhancement
frontend
help wanted
invalid
issue-25
issue-26
issue-42
issue-65
issue-67
issue-82
migrations
p0-critical
p1-high
p2-medium
p3-low
performance
quality
question
security
state-management
testing
timezone
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
fastie81/honbu-manager#106
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Issue: Phase 1 — Dynamic Routing Infrastructure
Type: Architecture / Refactoring
Priority: High
Epic: Epic: Database-per-Club Multi-Tenancy Split & Data Migration
Status: Backlog
1. Context & Purpose
We need to swap database connections dynamically on a per-request basis. This issue implements the foundation: a thread-safe and async-safe routing context (
contextvars) and a customSQLAlchemyclass that routes queries based on that context.2. Technical Specification
2.1. Routing Context (
context.py)Create a new file app/infrastructure/database/context.py:
contextvars.ContextVarnamedrouting_club_idwith a default ofNone.set_routing_club_id(club_id: int): Sets the current club ID.get_routing_club_id() -> Optional[int]: Returns the current club ID orNone.clear_routing_club_id(): Resets the context var toNone.route_to_club(club_id: int)using@contextmanagerto temporarily swap club contexts and safely restore the old value in afinallyblock:2.2. Dynamic DB Router (
extensions.py)Modify app/infrastructure/flask/extensions.py:
SQLAlchemyintoDynamicRoutingSQLAlchemy.get_engine(self, bind=None):club_id = get_routing_club_id().club_idisNoneor0, return the default engine (super().get_engine(bind=bind)).club_idis set, check the cacheself._club_engines.database_nameordatabase_urifrom theclubstable:database_uriexists, use it.sqlalchemy.engine.url.make_url) and substitute the database name withhonbu_club_<club_id>.db_orm = DynamicRoutingSQLAlchemy().2.3. Request Lifecycle Integration (
app_factory.py)Modify the Flask app factory to automatically clear the routing context after every request:
2.4. Auth Decorator Integration (
authentication.py)Modify app/core/auth/authentication.py:
require_authto callset_routing_club_id(user.get('club_id'))after successfully extracting the JWT token.require_club_accessto callset_routing_club_id(club_id).3. Verification Plan
3.1. Automated Unit Tests
Write tests in
tests/unit/test_routing_infra.py:set_routing_club_idandget_routing_club_idwork across threads/coroutines.SQLAlchemy.get_engineand verify that calling a query inside awith route_to_club(1):context switches engines correctly, and returns to the default engine afterwards.3.2. Integration Verification
pytest): All tests must pass. The default routing behavior must remain unchanged because no club database configurations have been set.Part of Epic #105