# Personal Finances API - Architecture Documentation ## Overview This backend API is built following **SOLID principles** and **clean architecture** patterns using TypeScript, Fastify, and PostgreSQL. ## SOLID Principles Implementation ### 1. Single Responsibility Principle (SRP) Each class has one well-defined responsibility: - **Controllers** - Handle HTTP requests/responses only - **Services** - Contain business logic only - **Repositories** - Handle data access only - **Middleware** - Handle cross-cutting concerns (auth, errors) Example: ```typescript // AssetService - ONLY handles asset business logic export class AssetService { async create(userId: string, data: CreateAssetDTO): Promise async update(id: string, userId: string, data: UpdateAssetDTO): Promise // ... } // AssetRepository - ONLY handles database operations export class AssetRepository { async findById(id: string): Promise async create(data: Prisma.AssetCreateInput): Promise // ... } ``` ### 2. Open/Closed Principle (OCP) The system is open for extension but closed for modification: - **Custom Error Classes** - Extend `AppError` base class for new error types - **Repository Interfaces** - Implement `IRepository` for new entities - **Service Pattern** - Add new services without modifying existing ones Example: ```typescript // Extensible error hierarchy export abstract class AppError extends Error { abstract statusCode: number; } export class NotFoundError extends AppError { statusCode = 404; } export class ValidationError extends AppError { statusCode = 400; } ``` ### 3. Liskov Substitution Principle (LSP) Derived classes can substitute their base classes: ```typescript // Base interface export interface IRepository { findById(id: string): Promise; create(data: Partial): Promise; } // Specialized interface extends base without breaking it export interface IUserScopedRepository extends Omit, 'findAll'> { findAllByUser(userId: string): Promise; } ``` ### 4. Interface Segregation Principle (ISP) Clients depend only on interfaces they use: - `IRepository` - Base CRUD operations - `IUserScopedRepository` - User-scoped operations - Specific methods in services (e.g., `getTotalValue()` in AssetService) ### 5. Dependency Inversion Principle (DIP) High-level modules depend on abstractions: ```typescript // Service depends on repository abstraction, not concrete implementation export class AuthService { constructor(private userRepository: UserRepository) {} // UserRepository implements IRepository } // Singleton database connection class DatabaseConnection { private static instance: PrismaClient; public static getInstance(): PrismaClient { // ... } } ``` ## Architecture Layers ### 1. Presentation Layer (Controllers & Routes) - **Location**: `src/controllers/`, `src/routes/` - **Purpose**: Handle HTTP requests/responses - **Responsibilities**: - Parse request parameters - Validate input schemas (using Zod) - Call service layer - Format responses ```typescript export class AssetController { async create(request: FastifyRequest, reply: FastifyReply) { const userId = getUserId(request); const data = createAssetSchema.parse(request.body); // Validation const asset = await this.assetService.create(userId, data); // Business logic return reply.status(201).send({asset}); // Response } } ``` ### 2. Business Logic Layer (Services) - **Location**: `src/services/` - **Purpose**: Implement business rules - **Responsibilities**: - Validate business rules - Coordinate between repositories - Perform calculations - Enforce authorization ```typescript export class InvoiceService { async create(userId: string, data: CreateInvoiceDTO): Promise { this.validateInvoiceData(data); // Business rule const invoiceNumber = await this.generateInvoiceNumber(userId); // Logic const subtotal = this.calculateSubtotal(data.lineItems); // Calculation return this.invoiceRepository.create({...}); // Data access } } ``` ### 3. Data Access Layer (Repositories) - **Location**: `src/repositories/` - **Purpose**: Abstract database operations - **Responsibilities**: - CRUD operations - Query composition - Data mapping ```typescript export class AssetRepository implements IUserScopedRepository { async findAllByUser(userId: string): Promise { return prisma.asset.findMany({ where: {userId}, orderBy: {createdAt: 'desc'}, }); } } ``` ### 4. Cross-Cutting Concerns (Middleware & Utils) - **Location**: `src/middleware/`, `src/utils/` - **Purpose**: Handle common functionality - **Components**: - Authentication middleware - Error handling - Password utilities - Custom errors ## Data Flow ``` HTTP Request ↓ Route (Fastify) ↓ Controller ├→ Validate Input (Zod) ├→ Extract User ID (Middleware) └→ Call Service ↓ Service ├→ Validate Business Rules ├→ Perform Calculations └→ Call Repository ↓ Repository ├→ Compose Query └→ Execute via Prisma ↓ Database (PostgreSQL) ``` ## Database Schema ### Entity Relationships ``` User ├── Assets (1:N) ├── Liabilities (1:N) ├── NetWorthSnapshots (1:N) ├── Clients (1:N) │ └── Invoices (1:N) │ └── InvoiceLineItems (1:N) ├── IncomeSources (1:N) ├── Expenses (1:N) ├── Transactions (1:N) └── DebtCategories (1:N) └── DebtAccounts (1:N) └── DebtPayments (1:N) ``` ### Key Design Decisions 1. **Cascade Deletes**: User deletion cascades to all related data 2. **UUID Primary Keys**: For security and distributed systems 3. **Timestamps**: All entities track creation/update times 4. **Enums**: Type-safe status and category fields 5. **Composite Indexes**: Optimized queries on user_id + other fields ## Security Features ### 1. Authentication - JWT tokens with configurable expiration - Secure password hashing (bcrypt with 10 rounds) - Password complexity requirements ### 2. Authorization - User-scoped data access - Repository methods verify ownership - Middleware extracts authenticated user ### 3. Input Validation - Zod schemas for runtime validation - Type-safe request/response handling - SQL injection prevention (Prisma ORM) ### 4. Error Handling - Custom error classes - No sensitive information in error messages - Proper HTTP status codes ## API Design ### RESTful Conventions - `GET /api/resources` - List all - `GET /api/resources/:id` - Get one - `POST /api/resources` - Create - `PUT /api/resources/:id` - Update (full) - `PATCH /api/resources/:id` - Update (partial) - `DELETE /api/resources/:id` - Delete ### Response Format ```json { "resource": { /* data */ }, // or "resources": [ /* array */ ], // or on error "error": "ErrorType", "message": "Human-readable message" } ``` ### HTTP Status Codes - `200 OK` - Successful GET/PUT/PATCH - `201 Created` - Successful POST - `204 No Content` - Successful DELETE - `400 Bad Request` - Validation error - `401 Unauthorized` - Authentication required - `403 Forbidden` - Insufficient permissions - `404 Not Found` - Resource not found - `409 Conflict` - Duplicate resource - `500 Internal Server Error` - Server error ## Testing Strategy ### Unit Tests - Test services in isolation - Mock repository dependencies - Test business logic thoroughly ### Integration Tests - Test API endpoints - Use test database - Verify request/response flow ### E2E Tests - Test complete user flows - Verify authentication - Test error scenarios ## Performance Considerations ### Database - Indexes on frequently queried fields - Connection pooling (Prisma) - Efficient query composition ### Caching - JWT tokens cached in client - Consider Redis for session management - Database query result caching ### Scalability - Stateless API (horizontal scaling) - Database migrations for schema changes - Environment-based configuration ## Development Workflow 1. **Add New Feature**: - Define Prisma schema - Run migration - Create repository interface - Implement repository - Create service with business logic - Add controller - Define routes - Add tests 2. **Modify Existing Feature**: - Update service layer (business logic) - Update repository if needed - Update schema if needed - Update tests ## Best Practices ### Code Organization ✅ One class per file ✅ Group related files in directories ✅ Use barrel exports (index.ts) ✅ Consistent naming conventions ### Error Handling ✅ Use custom error classes ✅ Validate at boundaries ✅ Log errors appropriately ✅ Return user-friendly messages ### Security ✅ Never log sensitive data ✅ Validate all inputs ✅ Use parameterized queries (Prisma) ✅ Implement rate limiting ✅ Keep dependencies updated ### Documentation ✅ JSDoc comments for public APIs ✅ README for setup instructions ✅ API documentation (Swagger) ✅ Architecture documentation (this file) ## Future Enhancements - [ ] Rate limiting middleware - [ ] Request logging - [ ] Metrics and monitoring - [ ] Database query optimization - [ ] Caching layer - [ ] WebSocket support for real-time updates - [ ] Background job processing - [ ] Email notifications - [ ] Data export functionality - [ ] Multi-tenancy support