All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 16m0s
546 lines
20 KiB
Markdown
546 lines
20 KiB
Markdown
# ADR-047: Project File and Folder Organization
|
|
|
|
**Date**: 2026-01-09
|
|
|
|
**Status**: Proposed
|
|
|
|
**Effort**: XL (Major reorganization across entire codebase)
|
|
|
|
## Context
|
|
|
|
The project has grown organically with a mix of organizational patterns:
|
|
|
|
- **By Type**: Components, hooks, middleware, utilities, types all in flat directories
|
|
- **By Feature**: Routes, database modules, and partial feature directories
|
|
- **Mixed Concerns**: Frontend and backend code intermingled in `src/`
|
|
|
|
Current pain points:
|
|
|
|
1. **Flat services directory**: 75+ files with no subdirectory grouping
|
|
2. **Monolithic types.ts**: 750+ lines, unclear when to add new types
|
|
3. **Flat components directory**: 43+ components at root level
|
|
4. **Incomplete feature modules**: Features contain only UI, not domain logic
|
|
5. **No clear frontend/backend separation**: Both share `src/` root
|
|
|
|
As the project scales, these issues compound, making navigation, refactoring, and onboarding increasingly difficult.
|
|
|
|
## Decision
|
|
|
|
We will adopt a **domain-driven organization** with clear separation between:
|
|
|
|
1. **Client code** (React, browser-only)
|
|
2. **Server code** (Express, Node-only)
|
|
3. **Shared code** (Types, utilities used by both)
|
|
|
|
Within each layer, organize by **feature/domain** rather than by file type.
|
|
|
|
### Design Principles
|
|
|
|
- **Colocation**: Related code lives together (components, hooks, types, tests)
|
|
- **Explicit Boundaries**: Clear separation between client, server, and shared
|
|
- **Feature Ownership**: Each domain owns its entire vertical slice
|
|
- **Discoverability**: New developers can find code by thinking about features, not file types
|
|
- **Incremental Migration**: Structure supports gradual transition from current layout
|
|
|
|
## Target Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── client/ # React frontend (browser-only code)
|
|
│ ├── app/ # App shell and routing
|
|
│ │ ├── App.tsx
|
|
│ │ ├── routes.tsx
|
|
│ │ └── providers/ # React context providers
|
|
│ │ ├── AppProviders.tsx
|
|
│ │ ├── AuthProvider.tsx
|
|
│ │ ├── FlyersProvider.tsx
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── features/ # Feature modules (UI + hooks + types)
|
|
│ │ ├── auth/
|
|
│ │ │ ├── components/
|
|
│ │ │ │ ├── LoginForm.tsx
|
|
│ │ │ │ ├── RegisterForm.tsx
|
|
│ │ │ │ └── index.ts
|
|
│ │ │ ├── hooks/
|
|
│ │ │ │ ├── useAuth.ts
|
|
│ │ │ │ ├── useLogin.ts
|
|
│ │ │ │ └── index.ts
|
|
│ │ │ ├── types.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── flyer/
|
|
│ │ │ ├── components/
|
|
│ │ │ │ ├── FlyerCard.tsx
|
|
│ │ │ │ ├── FlyerGrid.tsx
|
|
│ │ │ │ ├── FlyerUploader.tsx
|
|
│ │ │ │ ├── BulkImporter.tsx
|
|
│ │ │ │ └── index.ts
|
|
│ │ │ ├── hooks/
|
|
│ │ │ │ ├── useFlyersQuery.ts
|
|
│ │ │ │ ├── useFlyerUploadMutation.ts
|
|
│ │ │ │ └── index.ts
|
|
│ │ │ ├── types.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── shopping/
|
|
│ │ │ ├── components/
|
|
│ │ │ ├── hooks/
|
|
│ │ │ ├── types.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── recipes/
|
|
│ │ │ ├── components/
|
|
│ │ │ ├── hooks/
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── charts/
|
|
│ │ │ ├── components/
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── voice-assistant/
|
|
│ │ │ ├── components/
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── user/
|
|
│ │ │ ├── components/
|
|
│ │ │ ├── hooks/
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── gamification/
|
|
│ │ │ ├── components/
|
|
│ │ │ ├── hooks/
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ └── admin/
|
|
│ │ ├── components/
|
|
│ │ ├── hooks/
|
|
│ │ ├── pages/ # Admin-specific pages
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── pages/ # Route page components
|
|
│ │ ├── HomePage.tsx
|
|
│ │ ├── MyDealsPage.tsx
|
|
│ │ ├── UserProfilePage.tsx
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── components/ # Shared UI components
|
|
│ │ ├── ui/ # Primitive components (design system)
|
|
│ │ │ ├── Button.tsx
|
|
│ │ │ ├── Card.tsx
|
|
│ │ │ ├── Input.tsx
|
|
│ │ │ ├── Modal.tsx
|
|
│ │ │ ├── Badge.tsx
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── layout/ # Layout components
|
|
│ │ │ ├── Header.tsx
|
|
│ │ │ ├── Footer.tsx
|
|
│ │ │ ├── Sidebar.tsx
|
|
│ │ │ ├── PageLayout.tsx
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── feedback/ # User feedback components
|
|
│ │ │ ├── LoadingSpinner.tsx
|
|
│ │ │ ├── ErrorMessage.tsx
|
|
│ │ │ ├── Toast.tsx
|
|
│ │ │ ├── ConfirmDialog.tsx
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── forms/ # Form components
|
|
│ │ │ ├── FormField.tsx
|
|
│ │ │ ├── SearchInput.tsx
|
|
│ │ │ ├── DatePicker.tsx
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── icons/ # Icon components
|
|
│ │ │ ├── ChevronIcon.tsx
|
|
│ │ │ ├── UserIcon.tsx
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── hooks/ # Shared hooks (not feature-specific)
|
|
│ │ ├── useDebounce.ts
|
|
│ │ ├── useLocalStorage.ts
|
|
│ │ ├── useMediaQuery.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── services/ # Client-side services (API clients)
|
|
│ │ ├── apiClient.ts
|
|
│ │ ├── logger.client.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── lib/ # Third-party library wrappers
|
|
│ │ ├── queryClient.ts
|
|
│ │ ├── toast.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ └── styles/ # Global styles
|
|
│ ├── globals.css
|
|
│ └── tailwind.css
|
|
│
|
|
├── server/ # Express backend (Node-only code)
|
|
│ ├── app.ts # Express app setup
|
|
│ ├── server.ts # Server entry point
|
|
│ │
|
|
│ ├── domains/ # Domain modules (business logic)
|
|
│ │ ├── auth/
|
|
│ │ │ ├── auth.service.ts
|
|
│ │ │ ├── auth.routes.ts
|
|
│ │ │ ├── auth.controller.ts
|
|
│ │ │ ├── auth.repository.ts
|
|
│ │ │ ├── auth.types.ts
|
|
│ │ │ ├── auth.service.test.ts
|
|
│ │ │ ├── auth.routes.test.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── flyer/
|
|
│ │ │ ├── flyer.service.ts
|
|
│ │ │ ├── flyer.routes.ts
|
|
│ │ │ ├── flyer.controller.ts
|
|
│ │ │ ├── flyer.repository.ts
|
|
│ │ │ ├── flyer.types.ts
|
|
│ │ │ ├── flyer.processing.ts # Flyer-specific processing logic
|
|
│ │ │ ├── flyer.ai.ts # AI integration for flyers
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── user/
|
|
│ │ │ ├── user.service.ts
|
|
│ │ │ ├── user.routes.ts
|
|
│ │ │ ├── user.controller.ts
|
|
│ │ │ ├── user.repository.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── shopping/
|
|
│ │ │ ├── shopping.service.ts
|
|
│ │ │ ├── shopping.routes.ts
|
|
│ │ │ ├── shopping.repository.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── recipe/
|
|
│ │ │ ├── recipe.service.ts
|
|
│ │ │ ├── recipe.routes.ts
|
|
│ │ │ ├── recipe.repository.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── gamification/
|
|
│ │ │ ├── gamification.service.ts
|
|
│ │ │ ├── gamification.routes.ts
|
|
│ │ │ ├── gamification.repository.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── notification/
|
|
│ │ │ ├── notification.service.ts
|
|
│ │ │ ├── email.service.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── ai/
|
|
│ │ │ ├── ai.service.ts
|
|
│ │ │ ├── ai.client.ts
|
|
│ │ │ ├── ai.prompts.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ └── admin/
|
|
│ │ ├── admin.routes.ts
|
|
│ │ ├── admin.controller.ts
|
|
│ │ ├── admin.service.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── middleware/ # Express middleware
|
|
│ │ ├── auth.middleware.ts
|
|
│ │ ├── validation.middleware.ts
|
|
│ │ ├── errorHandler.middleware.ts
|
|
│ │ ├── rateLimit.middleware.ts
|
|
│ │ ├── fileUpload.middleware.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── infrastructure/ # Cross-cutting infrastructure
|
|
│ │ ├── database/
|
|
│ │ │ ├── pool.ts
|
|
│ │ │ ├── migrations/
|
|
│ │ │ └── seeds/
|
|
│ │ │
|
|
│ │ ├── cache/
|
|
│ │ │ ├── redis.ts
|
|
│ │ │ └── cacheService.ts
|
|
│ │ │
|
|
│ │ ├── queue/
|
|
│ │ │ ├── queueService.ts
|
|
│ │ │ ├── workers/
|
|
│ │ │ │ ├── email.worker.ts
|
|
│ │ │ │ ├── flyer.worker.ts
|
|
│ │ │ │ └── index.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── jobs/
|
|
│ │ │ ├── cronJobs.ts
|
|
│ │ │ ├── dailyAnalytics.job.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ └── logging/
|
|
│ │ ├── logger.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── config/ # Server configuration
|
|
│ │ ├── database.config.ts
|
|
│ │ ├── redis.config.ts
|
|
│ │ ├── auth.config.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ └── utils/ # Server-only utilities
|
|
│ ├── imageProcessor.ts
|
|
│ ├── geocoding.ts
|
|
│ └── index.ts
|
|
│
|
|
├── shared/ # Code shared between client and server
|
|
│ ├── types/ # Shared TypeScript types
|
|
│ │ ├── entities/ # Domain entities
|
|
│ │ │ ├── flyer.types.ts
|
|
│ │ │ ├── user.types.ts
|
|
│ │ │ ├── shopping.types.ts
|
|
│ │ │ ├── recipe.types.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ ├── api/ # API contract types
|
|
│ │ │ ├── requests.ts
|
|
│ │ │ ├── responses.ts
|
|
│ │ │ ├── errors.ts
|
|
│ │ │ └── index.ts
|
|
│ │ │
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── schemas/ # Zod validation schemas
|
|
│ │ ├── flyer.schema.ts
|
|
│ │ ├── user.schema.ts
|
|
│ │ ├── auth.schema.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── constants/ # Shared constants
|
|
│ │ ├── categories.ts
|
|
│ │ ├── errorCodes.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ └── utils/ # Isomorphic utilities
|
|
│ ├── formatting.ts
|
|
│ ├── validation.ts
|
|
│ └── index.ts
|
|
│
|
|
├── tests/ # Test infrastructure
|
|
│ ├── setup/
|
|
│ │ ├── vitest.setup.ts
|
|
│ │ └── testDb.setup.ts
|
|
│ │
|
|
│ ├── fixtures/
|
|
│ │ ├── mockFactories.ts
|
|
│ │ ├── sampleFlyers/
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── utils/
|
|
│ │ ├── testHelpers.ts
|
|
│ │ └── index.ts
|
|
│ │
|
|
│ ├── integration/ # Integration tests
|
|
│ │ ├── api/
|
|
│ │ └── database/
|
|
│ │
|
|
│ └── e2e/ # End-to-end tests
|
|
│ └── flows/
|
|
│
|
|
├── scripts/ # Build and utility scripts
|
|
│ ├── seed.ts
|
|
│ ├── migrate.ts
|
|
│ └── generateTypes.ts
|
|
│
|
|
└── index.tsx # Client entry point
|
|
```
|
|
|
|
## Domain Module Structure
|
|
|
|
Each server domain follows a consistent structure:
|
|
|
|
```
|
|
domains/flyer/
|
|
├── flyer.service.ts # Business logic
|
|
├── flyer.routes.ts # Express routes
|
|
├── flyer.controller.ts # Route handlers
|
|
├── flyer.repository.ts # Database access
|
|
├── flyer.types.ts # Domain-specific types
|
|
├── flyer.service.test.ts # Service tests
|
|
├── flyer.routes.test.ts # Route tests
|
|
└── index.ts # Public API
|
|
```
|
|
|
|
### Domain Index Pattern
|
|
|
|
Each domain exports a clean public API:
|
|
|
|
```typescript
|
|
// server/domains/flyer/index.ts
|
|
export { FlyerService } from './flyer.service';
|
|
export { flyerRoutes } from './flyer.routes';
|
|
export type { FlyerWithItems, FlyerCreateInput } from './flyer.types';
|
|
```
|
|
|
|
## Client Feature Module Structure
|
|
|
|
Each client feature follows a consistent structure:
|
|
|
|
```
|
|
client/features/flyer/
|
|
├── components/
|
|
│ ├── FlyerCard.tsx
|
|
│ ├── FlyerCard.test.tsx
|
|
│ ├── FlyerGrid.tsx
|
|
│ └── index.ts
|
|
├── hooks/
|
|
│ ├── useFlyersQuery.ts
|
|
│ ├── useFlyerUploadMutation.ts
|
|
│ └── index.ts
|
|
├── types.ts # Feature-specific client types
|
|
└── index.ts # Public API
|
|
```
|
|
|
|
## Import Path Aliases
|
|
|
|
Configure TypeScript and bundler for clean imports:
|
|
|
|
```typescript
|
|
// tsconfig.json paths
|
|
{
|
|
"paths": {
|
|
"@/client/*": ["src/client/*"],
|
|
"@/server/*": ["src/server/*"],
|
|
"@/shared/*": ["src/shared/*"],
|
|
"@/tests/*": ["src/tests/*"]
|
|
}
|
|
}
|
|
|
|
// Usage examples
|
|
import { Button, Card } from '@/client/components/ui';
|
|
import { useFlyersQuery } from '@/client/features/flyer';
|
|
import { FlyerService } from '@/server/domains/flyer';
|
|
import type { Flyer } from '@/shared/types/entities';
|
|
```
|
|
|
|
## Migration Strategy
|
|
|
|
Given the scope of this reorganization, migrate incrementally:
|
|
|
|
### Phase 1: Create Directory Structure
|
|
|
|
1. Create `client/`, `server/`, `shared/` directories
|
|
2. Set up path aliases in tsconfig.json
|
|
3. Update build configuration (Vite)
|
|
|
|
### Phase 2: Migrate Shared Code
|
|
|
|
1. Move types to `shared/types/`
|
|
2. Move schemas to `shared/schemas/`
|
|
3. Move shared utils to `shared/utils/`
|
|
4. Update imports across codebase
|
|
|
|
### Phase 3: Migrate Server Code
|
|
|
|
1. Create `server/domains/` structure
|
|
2. Move one domain at a time (start with `auth` or `user`)
|
|
3. Move each service + routes + repository together
|
|
4. Update route registration in app.ts
|
|
5. Run tests after each domain migration
|
|
|
|
### Phase 4: Migrate Client Code
|
|
|
|
1. Create `client/features/` structure
|
|
2. Move components into features
|
|
3. Move hooks into features or shared hooks
|
|
4. Move pages to `client/pages/`
|
|
5. Organize shared components into categories
|
|
|
|
### Phase 5: Cleanup
|
|
|
|
1. Remove empty old directories
|
|
2. Update all remaining imports
|
|
3. Update CI/CD paths if needed
|
|
4. Update documentation
|
|
|
|
## Naming Conventions
|
|
|
|
| Item | Convention | Example |
|
|
| ----------------- | -------------------- | ----------------------- |
|
|
| Domain directory | lowercase | `flyer/`, `shopping/` |
|
|
| Feature directory | kebab-case | `voice-assistant/` |
|
|
| Service file | domain.service.ts | `flyer.service.ts` |
|
|
| Route file | domain.routes.ts | `flyer.routes.ts` |
|
|
| Repository file | domain.repository.ts | `flyer.repository.ts` |
|
|
| Component file | PascalCase.tsx | `FlyerCard.tsx` |
|
|
| Hook file | camelCase.ts | `useFlyersQuery.ts` |
|
|
| Type file | domain.types.ts | `flyer.types.ts` |
|
|
| Test file | \*.test.ts(x) | `flyer.service.test.ts` |
|
|
| Index file | index.ts | `index.ts` |
|
|
|
|
## File Placement Guidelines
|
|
|
|
**Where does this file go?**
|
|
|
|
| If the file is... | Place it in... |
|
|
| ------------------------------------ | ------------------------------------------------ |
|
|
| Used only by React | `client/` |
|
|
| Used only by Express/Node | `server/` |
|
|
| TypeScript types used by both | `shared/types/` |
|
|
| Zod schemas | `shared/schemas/` |
|
|
| React component for one feature | `client/features/{feature}/components/` |
|
|
| React component used across features | `client/components/` |
|
|
| React hook for one feature | `client/features/{feature}/hooks/` |
|
|
| React hook used across features | `client/hooks/` |
|
|
| Business logic for a domain | `server/domains/{domain}/` |
|
|
| Database access for a domain | `server/domains/{domain}/{domain}.repository.ts` |
|
|
| Express middleware | `server/middleware/` |
|
|
| Background job worker | `server/infrastructure/queue/workers/` |
|
|
| Cron job definition | `server/infrastructure/jobs/` |
|
|
| Test factory/fixture | `tests/fixtures/` |
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
|
|
- **Clear Boundaries**: Frontend, backend, and shared code are explicitly separated
|
|
- **Feature Discoverability**: Find all code for a feature in one place
|
|
- **Parallel Development**: Teams can work on domains independently
|
|
- **Easier Refactoring**: Domain boundaries make changes localized
|
|
- **Better Onboarding**: New developers navigate by feature, not file type
|
|
- **Scalability**: Structure supports growth without becoming unwieldy
|
|
|
|
### Negative
|
|
|
|
- **Large Migration Effort**: Significant one-time cost (XL effort)
|
|
- **Import Updates**: All imports need updating
|
|
- **Learning Curve**: Team must learn new structure
|
|
- **Merge Conflicts**: In-flight PRs will need rebasing
|
|
|
|
### Mitigation
|
|
|
|
- Use automated tools (e.g., `ts-morph`) to update imports
|
|
- Migrate one domain/feature at a time
|
|
- Create a migration checklist and track progress
|
|
- Coordinate with team to minimize in-flight work during migration phases
|
|
- Consider using feature flags to ship incrementally
|
|
|
|
## Key Differences from Current Structure
|
|
|
|
| Aspect | Current | Target |
|
|
| ---------------- | -------------------------- | ----------------------------------------- |
|
|
| Frontend/Backend | Mixed in `src/` | Separated in `client/` and `server/` |
|
|
| Services | Flat directory (75+ files) | Grouped by domain |
|
|
| Components | Flat directory (43+ files) | Categorized (ui, layout, feedback, forms) |
|
|
| Types | Monolithic `types.ts` | Split by entity in `shared/types/` |
|
|
| Features | UI-only | Full vertical slice (UI + hooks + types) |
|
|
| Routes | Separate from services | Co-located in domain |
|
|
| Tests | Co-located + `tests/` | Co-located + `tests/` for fixtures |
|
|
|
|
## Related ADRs
|
|
|
|
- [ADR-034](./0034-repository-pattern-standards.md) - Repository Pattern (affects domain structure)
|
|
- [ADR-035](./0035-service-layer-architecture.md) - Service Layer (affects domain structure)
|
|
- [ADR-044](./0044-frontend-feature-organization.md) - Frontend Features (this ADR supersedes it)
|
|
- [ADR-045](./0045-test-data-factories-and-fixtures.md) - Test Fixtures (affects tests/ directory)
|