๐Ÿ— Architect view

Coupling, patterns, Zones of Pain, refactoring priorities  ยท  Software Architect, Principal Engineer

Generated 6 hours ago
โšก Agent trace  ยท  13 steps  ยท  $0.0369  ยท  click to expand
search_claims(query='architecture layered event-driven monolithic', category='structural') โ†’ search_claims(query='high coupling dependency module', category='structural') โ†’ search_claims(query='design pattern factory singleton observer', category='structural') โ†’ search_claims(category='structural', query='FastAPI router endpoint API route') โ†’ search_claims(category='structural', query='model database table SQLAlchemy') โ†’ search_claims(category='structural', query='React component hook context provider') โ†’ get_dependencies(file_path='backend/app/api/main.py') โ†’ get_dependencies(file_path='backend/app/main.py') โ†’ get_dependencies(file_path='backend/app/api/deps.py') โ†’ get_dependencies(file_path='backend/app/models.py') โ†’ get_dependencies(file_path='backend/app/api/routes/users.py') โ†’ get_dependencies(file_path='backend/app/api/routes/items.py') โ†’ get_dependencies(file_path='backend/app/api/routes/login.py') โ†’ get_dependencies(file_path='backend/app/api/routes/private.py') โ†’ get_dependencies(file_path='backend/app/api/routes/utils.py') โ†’ get_dependencies(file_path='frontend/src/hooks/useAuth.ts') โ†’ get_dependencies(file_path='frontend/src/client/sdk.gen.ts') โ†’ get_dependencies(file_path='frontend/src/routes/_layout.tsx') โ†’ get_dependencies(file_path='frontend/src/components/ui/sidebar.tsx') โ†’ get_file_claims(file_path='frontend/src/hooks/useAuth.ts') โ†’ get_file_claims(file_path='backend/app/utils.py') โ†’ get_dependencies(file_path='frontend/src/client/index.ts') โ†’ get_dependencies(file_path='frontend/src/routeTree.gen.ts') โ†’ read_source_file(file_path='frontend/src/hooks/useAuth.ts', start_line=1) โ†’ get_dependencies(file_path='frontend/src/utils.ts') โ†’ get_dependencies(file_path='frontend/src/main.tsx') โ†’ read_source_file(end_line=60, file_path='backend/app/main.py') โ†’ read_source_file(end_line=60, file_path='backend/app/api/main.py') โ†’ read_source_file(end_line=60, file_path='backend/app/api/deps.py') โ†’ read_source_file(end_line=70, file_path='backend/app/models.py') โ†’ get_file_claims(file_path='backend/app/core/config.py') โ†’ get_file_claims(file_path='backend/app/core/security.py') โ†’ read_source_file(end_line=80, file_path='backend/app/api/routes/users.py') โ†’ read_source_file(end_line=80, file_path='backend/app/api/routes/items.py') โ†’ get_file_claims(file_path='backend/app/crud.py') โ†’ get_dependencies(file_path='backend/app/crud.py') โ†’ read_source_file(end_line=60, file_path='backend/app/crud.py') โ†’ get_dependencies(file_path='frontend/src/components/Sidebar/AppSidebar.tsx') โ†’ get_dependencies(file_path='frontend/src/components/Admin/UserActionsMenu.tsx') โ†’ get_dependencies(file_path='frontend/src/components/UserSettings/UserInformation.tsx') โ†’ get_dependencies(file_path='frontend/src/components/Items/columns.tsx')

Architectural Analysis

Architecture Overview

This is a full-stack monolithic application following the well-known FastAPI Full-Stack Template pattern. The backend is a layered FastAPI service (routes โ†’ deps โ†’ crud โ†’ models/core) using SQLModel/SQLAlchemy with PostgreSQL, JWT auth, and SMTP email. The frontend is a React SPA using TanStack Router (file-based routing), TanStack Query for server state, and a generated OpenAPI client SDK (@hey-api/openapi-ts). The two halves communicate exclusively through a REST API contract โ€” the frontend's sdk.gen.ts and types.gen.ts are auto-generated from the backend's OpenAPI spec, which is a healthy contract-first boundary. The backend is cleanly layered but has a notable models.py monolith mixing DB tables, Pydantic schemas, and API DTOs in one file.

Zones of Pain

1. backend/app/models.py โ€” The God Module (HIGH)

This single file (backend/app/models.py:1-61) conflates four distinct concerns: database table models (User, Item at lines 49-56), Pydantic input schemas (UserCreate, UserRegister, UserUpdate, UpdatePassword at lines 22-46), API response DTOs (UserPublic, UsersPublic, ItemPublic, ItemsPublic), and shared base classes (UserBase at line 14). Every route file imports from it directly: - backend/app/api/routes/users.py:15-26 imports 10+ model classes - backend/app/api/routes/items.py:8 imports 7 model classes - backend/app/crud.py:7 imports 6 model classes - backend/app/api/deps.py:14 imports TokenPayload and User

This violates the Single Responsibility Principle and creates a change amplification problem: any schema change (e.g., adding a field to UserBase) ripples through routes, CRUD, and deps simultaneously. The file is also where TokenPayload lives โ€” a JWT concern that has nothing to do with database models.

2. backend/app/api/deps.py โ€” Auth Hub (HIGH)

deps.py is the authentication bottleneck. It defines SessionDep, TokenDep, CurrentUser, and get_current_active_superuser (backend/app/api/deps.py:26-57), and every protected route imports from it. It also directly imports engine from app.core.db (line 13) and User from app.models (line 14), coupling the auth layer to the DB engine and model layer. Any change to session management or user model ripples through every route.

3. frontend/src/hooks/useAuth.ts โ€” Frontend Auth Hub (HIGH)

useAuth is imported by 13 files (all routes under _layout, login/signup/recover/reset pages, and 5+ components). It bundles authentication state, sign-up/login/logout mutations, and user fetching into one hook (frontend/src/hooks/useAuth.ts:18-67). It also directly manipulates localStorage for token storage (line 15, 41-46, 56-59) โ€” a security-sensitive concern embedded in a UI hook. Any change to auth flow touches nearly every page in the app.

Coupling Analysis

Module In-Degree Out-Degree Concern Level Reasoning
backend/app/models.py ~8 (routes, crud, deps) ~4 (sqlmodel, pydantic) HIGH God module mixing DB/schema/DTO concerns; every schema change ripples everywhere
backend/app/api/deps.py ~5 (all route files) ~4 (models, core.config, core.security, core.db) HIGH Auth bottleneck; couples auth to DB engine and model layer
backend/app/core/config.py ~6 (main, deps, security, utils, routes) ~2 MEDIUM Module-level settings singleton (config.py:26-116) is imported everywhere; fine for config but creates implicit global state
frontend/src/hooks/useAuth.ts 13 ~4 HIGH Auth hub for entire frontend; localStorage coupling embedded in hook
frontend/src/client/index.ts 3 (main, utils, tests) 5 (core + sdk + types) LOW Clean auto-generated SDK boundary
frontend/src/routeTree.gen.ts 1 (main) 10 (all routes) LOW Auto-generated; regenerated on route change
frontend/src/components/ui/sidebar.tsx 5 8 MEDIUM Complex shadcn/ui composite; imports 8 UI primitives
backend/app/crud.py ~3 (routes) ~3 (models, security) MEDIUM Thin data-access layer but routes bypass it with inline SQLModel queries (see below)

Critical finding โ€” dependency graph false positive: The dependency graph reports frontend/src/hooks/useAuth.ts and frontend/src/components/UserSettings/UserInformation.tsx importing backend/app/utils.py. Reading the actual source (frontend/src/hooks/useAuth.ts:1-12) confirms this is impossible โ€” the frontend imports from @/client and @/utils, not Python files. This is a name-collision artifact (both utils.py and utils.ts matched). The graph should be treated with caution for cross-language edges.

Design Pattern Inventory

Pattern Location Consistency Assessment
Dependency Injection (FastAPI Depends) backend/app/api/deps.py:26-49 Good SessionDep, TokenDep, CurrentUser type aliases are clean and idiomatic
Repository/CRUD layer backend/app/crud.py:10-68 Inconsistent CRUD exists for users/items but routes bypass it with inline SQLModel queries โ€” e.g., users.py:42-51 and items.py:21-42 do raw select/count in the route handler instead of delegating to crud
DTO/Schema separation backend/app/models.py Broken DB tables, input schemas, and response DTOs all in one file โ€” no schemas.py separation
Singleton config backend/app/core/config.py:26-116 Good Module-level settings instantiation is standard for FastAPI
Provider/Context (React) frontend/src/components/theme-provider.tsx:31-106, frontend/src/components/ui/sidebar.tsx:8-20 Good Context + custom hook pattern is consistent
Custom hooks useAuth, useCustomToast, useCopyToClipboard, useMobile Good Consistent hook-per-concern pattern
Generated client SDK frontend/src/client/* Excellent Auto-generated from OpenAPI; contract-first boundary is the strongest architectural decision here
File-based routing frontend/src/routes/* + routeTree.gen.ts Good TanStack Router convention; layout route _layout.tsx wraps authenticated pages

Refactoring Priorities

1. Split backend/app/models.py into layered modules (HIGH impact, 1-2 sprints)

Problem: God module mixing DB tables, Pydantic schemas, and DTOs. Every schema change ripples through routes, crud, and deps. Action: Create app/schemas/ with user.py, item.py, token.py for Pydantic models; keep app/models.py for SQLModel table classes only. Move TokenPayload to a schemas/token.py. Benefit: Decouples API contract from persistence; enables independent evolution of DB schema vs. API DTOs; reduces merge conflicts and change blast radius.

2. Consolidate data access into crud.py (HIGH impact, 1 sprint)

Problem: Routes like users.py:42-51 and items.py:21-42 perform raw SQLModel queries inline, duplicating logic and bypassing the existing CRUD layer. The read_items handler (items.py:21-42) has a complex superuser-vs-owner branching query embedded in the route. Action: Extract read_users, read_items, get_item_by_id into crud.py with proper pagination helpers. Routes should only handle HTTP concerns (auth, validation, response shaping). Benefit: Single place to test query logic; routes become thin HTTP adapters; enables future caching or query optimization without touching route code.

3. Decouple useAuth from localStorage and reduce its fan-out (MEDIUM impact, 1 sprint)

Problem: useAuth is imported by 13 files and directly manipulates localStorage (useAuth.ts:15,41-46,56-59). Token storage is a security concern embedded in a UI hook. Action: Extract token storage into a dedicated authStorage module (or use a proper auth library). Consider splitting useAuth into useUser (query) and useAuthActions (mutations) so components only pull what they need. Benefit: Reduces re-render scope when auth state changes; makes token handling testable and auditable; lowers the blast radius of auth changes.

4. Fix the dependency graph false positives (LOW impact, quick win)

Problem: The graph reports frontend files importing backend/app/utils.py โ€” a cross-language impossibility caused by name collision between utils.py and utils.ts. Action: Namespace the dependency graph by language/package boundary, or rename frontend/src/utils.ts to something more specific (e.g., errorHandlers.ts). Benefit: Restores trust in the dependency graph as a structural analysis tool; prevents future misdiagnosis of coupling.

5. Consider extracting deps.py auth into a dedicated auth module (MEDIUM impact, 1 sprint)

Problem: deps.py couples auth to the DB engine (deps.py:13) and model layer (deps.py:14). Action: Move get_current_user and JWT decoding into app/core/auth.py or app/services/auth.py, keeping deps.py as a thin FastAPI dependency adapter. Benefit: Auth logic becomes testable without FastAPI dependency injection; decouples from the DB engine import; enables reuse in CLI scripts or background workers.


Overall assessment: This is a well-structured full-stack template with a strong contract-first boundary (generated SDK) and idiomatic FastAPI dependency injection. The most pressing structural debt is the models.py monolith and the inconsistent use of the CRUD layer โ€” both are classic symptoms of a codebase that grew from a template without a schema-separation discipline. The frontend is healthier, with the main concern being the over-centralized useAuth hook. All recommendations are achievable within single sprints and would meaningfully reduce change blast radius.