ADR-010: Monorepo Restructuring with UV Workspace
Status: Accepted Date: 2025-10-01 Deciders: niksacdev, Claude Code (system-architecture-reviewer) Tags: architecture, deployment, monorepo, infrastructure
Context
The Loan Defenders application started as a single Python package with mixed API and UI code. As the project grew, we identified several challenges:
- Deployment Complexity: Cannot deploy API and UI independently
- Dependency Conflicts: Shared dependencies between API (Python) and UI (TypeScript)
- Build Optimization: Cannot optimize Docker builds for each component
- Scaling Issues: Cannot scale API and UI separately based on load
- Development Experience: Changes to UI require rebuilding entire project
User Need
- Deploy components independently to different Azure Container Apps
- Scale services independently based on traffic patterns
- Optimize CI/CD by building only changed components
- Improve developer experience with faster iteration cycles
Decision
We restructure the repository into a monorepo with independent apps using the following architecture:
loan-defenders/
├── pyproject.toml # Workspace root (tooling config only)
├── .env # Shared environment variables
├── apps/
│ ├── api/ # Python FastAPI backend
│ │ ├── pyproject.toml # API-specific dependencies
│ │ ├── uv.lock # API dependency lock
│ │ └── loan_defenders/ # Application code
│ └── ui/ # TypeScript React frontend
│ ├── package.json # UI-specific dependencies
│ ├── package-lock.json
│ └── src/
├── tests/ # Shared test suite
└── docs/
Key Design Decisions
1. UV Workspace Pattern
# Root pyproject.toml
[tool.uv.workspace]
members = ["apps/api"]
# Shared tooling only (ruff, black, mypy, pytest)
[tool.uv]
dev-dependencies = [...]
Rationale: - Workspace allows shared development tools - Each app manages its own production dependencies - Lock files stay in app directories for independent deployment
2. Shared .env at Root
Rationale:
- Single source of truth for configuration
- Apps load from ../../.env using python-dotenv
- Simplifies local development
- Azure Container Apps can override per-service
3. Independent CI/CD
# .github/workflows/test-apps.yml
jobs:
test-api:
working-directory: apps/api
test-ui:
working-directory: apps/ui
Rationale: - Test each app independently - Deploy only changed components - Faster feedback loops
4. Container-Per-App Deployment
Rationale: - Each app builds its own optimized container - No cross-app dependencies in containers - Smaller image sizes (API ~300MB, UI ~50MB)
Consequences
Positive ✅
- Independent Deployment
- Deploy API without rebuilding UI
- Deploy UI without restarting API
-
Hotfix one service without affecting others
-
Independent Scaling
- Scale API horizontally based on request load
- Scale UI based on CDN cache misses
-
Right-size containers per service
-
Optimized CI/CD
- Run only affected tests
- Build only changed containers
-
Faster merge-to-production time
-
Better Developer Experience
- Frontend devs don't need Python environment
- Backend devs don't need Node.js
-
Faster local iteration (only rebuild changed app)
-
Future Extensibility
- Easy to add new apps (agents, workers, schedulers)
- Each app can use different tech stack
- Shared tooling ensures consistency
Negative ⚠️
- Migration Complexity
- One-time cost to restructure existing code
- Update all import paths
-
Migrate tests to new structure
-
Coordination Overhead
- Breaking API changes require UI updates
- Need versioning strategy for API contracts
-
Shared types need careful management
-
Local Development Setup
- Developers run multiple services locally
- Need docker-compose or scripts
-
More complex initial setup
-
Testing Complexity
- Integration tests span multiple apps
- Need proper test data management
- E2E tests require all services running
Mitigations
- Migration: Created comprehensive MIGRATION.md with step-by-step guide
- Coordination: Implement API versioning and OpenAPI contracts
- Local Dev: Created
scripts/dev-all.shto start all services - Testing: Shared
tests/directory for integration tests
Implementation
Phase 1: Restructure (✅ Complete)
- Create
apps/apiand move Python code - Create
apps/uiand move React code - Convert root to UV workspace
- Update import paths
- Move .env to root
- Update documentation
Phase 2: CI/CD (✅ Complete)
- Create
test-apps.ymlworkflow - Add workspace validation
- Configure Codecov for each app
Phase 3: Deployment (Pending)
- Create Azure Container Apps for each service
- Set up Azure Container Registry
- Configure deployment pipelines
- Set up monitoring and observability
Alternatives Considered
Alternative 1: Keep Single Package
Rejected: Cannot deploy components independently, violates cloud-native principles
Alternative 2: Separate Repositories
Rejected: Loses benefits of shared tooling, harder to maintain consistency, complicates versioning
Alternative 3: Nx/Turborepo
Rejected: Adds complexity, UV workspace sufficient for current needs, can migrate later if needed
References
- UV Workspace Documentation
- Azure Container Apps Multi-Container
- Monorepo Best Practices
- Original Discussion: PR #78
- Migration Guide:
MIGRATION.md
Related Decisions
- ADR-003: Instruction File Synchronization (workspace affects all IDEs)
- ADR-005: Orchestration Refactoring (affects agent deployment)
- Future: ADR-011: API Versioning Strategy
Decision Makers: niksacdev, Claude Code Consultation: system-architecture-reviewer agent (Architecture Grade: 7.9/10) Implementation: PR #78 - Complete Monorepo Restructuring