Files
personal-finance/backend-api/ARCHITECTURE.md
Alexander Zinn cd93dcbfd2 Add backend API for personal finance management application
- Introduced a comprehensive backend API using TypeScript, Fastify, and PostgreSQL.
- Added essential files including architecture documentation, environment configuration, and Docker setup.
- Implemented RESTful routes for managing assets, liabilities, clients, invoices, and cashflow.
- Established a robust database schema with Prisma for data management.
- Integrated middleware for authentication and error handling.
- Created service and repository layers to adhere to SOLID principles and clean architecture.
- Included example environment variables for development, staging, and production setups.
2025-12-07 12:59:09 -05:00

367 lines
9.3 KiB
Markdown

# 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<Asset>
async update(id: string, userId: string, data: UpdateAssetDTO): Promise<Asset>
// ...
}
// AssetRepository - ONLY handles database operations
export class AssetRepository {
async findById(id: string): Promise<Asset | null>
async create(data: Prisma.AssetCreateInput): Promise<Asset>
// ...
}
```
### 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<T>` 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<T> {
findById(id: string): Promise<T | null>;
create(data: Partial<T>): Promise<T>;
}
// Specialized interface extends base without breaking it
export interface IUserScopedRepository<T> extends Omit<IRepository<T>, 'findAll'> {
findAllByUser(userId: string): Promise<T[]>;
}
```
### 4. Interface Segregation Principle (ISP)
Clients depend only on interfaces they use:
- `IRepository<T>` - Base CRUD operations
- `IUserScopedRepository<T>` - 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<User>
}
// 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<Invoice> {
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<Asset> {
async findAllByUser(userId: string): Promise<Asset[]> {
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