- Introduced bun.lock and package-lock.json to manage dependencies for the project. - Enhanced backend API architecture documentation with additional security and documentation guidelines. - Made minor formatting adjustments across various files for consistency and clarity.
399 lines
9.3 KiB
Markdown
399 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
|