rwadurian/backend/services/backup-service/docs/DEVELOPMENT.md

514 lines
11 KiB
Markdown

# Backup Service Development Guide
## Prerequisites
- **Node.js:** v20.x or higher
- **npm:** v10.x or higher
- **Docker:** For running PostgreSQL locally
- **WSL2:** (Windows users) For running Docker and tests
---
## Quick Start
### 1. Clone and Install
```bash
# Navigate to service directory
cd backend/services/backup-service
# Install dependencies
npm install
```
### 2. Environment Setup
Copy the example environment file:
```bash
cp .env.example .env
```
Configure the following environment variables:
```bash
# Database Configuration
DATABASE_URL="postgresql://postgres:password@localhost:5433/rwa_backup?schema=public"
# Server Configuration
APP_PORT=3002
APP_ENV="development"
# Service-to-Service Authentication
SERVICE_JWT_SECRET="your-super-secret-service-jwt-key-min-32-chars"
ALLOWED_SERVICES="identity-service,recovery-service"
# Encryption
BACKUP_ENCRYPTION_KEY="your-256-bit-encryption-key-in-hex-64-chars"
BACKUP_ENCRYPTION_KEY_ID="key-v1"
# Rate Limiting
MAX_RETRIEVE_PER_DAY=3
MAX_STORE_PER_MINUTE=10
# Audit
AUDIT_LOG_RETENTION_DAYS=365
```
### 3. Start Database
```bash
# Start PostgreSQL container
docker-compose up -d
# Verify database is running
docker-compose ps
```
### 4. Setup Database Schema
```bash
# Generate Prisma client
npm run prisma:generate
# Run migrations
npm run prisma:migrate
```
### 5. Start Development Server
```bash
# Start with hot-reload
npm run start:dev
# Or start in debug mode
npm run start:debug
```
The service will be available at `http://localhost:3002`.
---
## Project Scripts
### Development
| Script | Description |
|--------|-------------|
| `npm run start` | Start the service |
| `npm run start:dev` | Start with hot-reload |
| `npm run start:debug` | Start with debugger |
| `npm run start:prod` | Start production build |
| `npm run build` | Build for production |
### Database
| Script | Description |
|--------|-------------|
| `npm run prisma:generate` | Generate Prisma client |
| `npm run prisma:migrate` | Run development migrations |
| `npm run prisma:migrate:prod` | Run production migrations |
| `npm run prisma:studio` | Open Prisma Studio GUI |
### Testing
| Script | Description |
|--------|-------------|
| `npm run test` | Run all tests |
| `npm run test:unit` | Run unit tests only |
| `npm run test:e2e:mock` | Run E2E tests with mocks |
| `npm run test:e2e:db` | Run E2E tests with real DB |
| `npm run test:cov` | Generate coverage report |
| `npm run test:watch` | Run tests in watch mode |
### Docker
| Script | Description |
|--------|-------------|
| `npm run docker:build` | Build Docker image |
| `npm run docker:up` | Start Docker compose stack |
| `npm run docker:down` | Stop Docker compose stack |
| `npm run db:test:up` | Start test database |
| `npm run db:test:down` | Stop test database |
---
## Code Structure
### Adding a New Feature
Follow the DDD + Hexagonal architecture pattern:
#### 1. Domain Layer (Core Business Logic)
Create entities and value objects first:
```typescript
// src/domain/entities/new-entity.entity.ts
export class NewEntity {
// Properties
private id: bigint;
private data: string;
// Factory method
static create(params: CreateParams): NewEntity {
// Validation and creation logic
return new NewEntity(params);
}
// Domain methods
performAction(): void {
// Business logic
}
}
```
#### 2. Repository Interface
Define the data access contract:
```typescript
// src/domain/repositories/new-entity.repository.interface.ts
export interface NewEntityRepository {
save(entity: NewEntity): Promise<NewEntity>;
findById(id: bigint): Promise<NewEntity | null>;
// ... other methods
}
```
#### 3. Application Layer (Use Cases)
Create commands/queries and handlers:
```typescript
// src/application/commands/create-new-entity/create-new-entity.command.ts
export class CreateNewEntityCommand {
constructor(
public readonly data: string,
// ... other fields
) {}
}
// src/application/commands/create-new-entity/create-new-entity.handler.ts
@Injectable()
export class CreateNewEntityHandler {
constructor(
@Inject('NewEntityRepository')
private readonly repository: NewEntityRepository,
) {}
async execute(command: CreateNewEntityCommand): Promise<Result> {
const entity = NewEntity.create({ data: command.data });
await this.repository.save(entity);
return { id: entity.id.toString() };
}
}
```
#### 4. Infrastructure Layer
Implement the repository:
```typescript
// src/infrastructure/persistence/repositories/new-entity.repository.impl.ts
@Injectable()
export class NewEntityRepositoryImpl implements NewEntityRepository {
constructor(private readonly prisma: PrismaService) {}
async save(entity: NewEntity): Promise<NewEntity> {
const data = await this.prisma.newEntity.create({
data: this.toDatabase(entity),
});
return this.toDomain(data);
}
// Mapping methods
private toDatabase(entity: NewEntity) { /* ... */ }
private toDomain(data: PrismaModel) { /* ... */ }
}
```
#### 5. API Layer
Create controller and DTOs:
```typescript
// src/api/dto/request/create-new-entity.dto.ts
export class CreateNewEntityDto {
@IsNotEmpty()
@IsString()
data: string;
}
// src/api/controllers/new-entity.controller.ts
@Controller('new-entity')
@UseGuards(ServiceAuthGuard)
export class NewEntityController {
constructor(
private readonly service: NewEntityApplicationService,
) {}
@Post()
async create(@Body() dto: CreateNewEntityDto) {
return this.service.create(dto);
}
}
```
---
## Environment Variables
### Required Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://...` |
| `SERVICE_JWT_SECRET` | JWT secret (min 32 chars) | `your-secret-key...` |
| `ALLOWED_SERVICES` | Comma-separated service list | `identity-service,recovery-service` |
| `BACKUP_ENCRYPTION_KEY` | 256-bit key in hex (64 chars) | `0123456789abcdef...` |
| `BACKUP_ENCRYPTION_KEY_ID` | Key identifier for rotation | `key-v1` |
### Optional Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `APP_PORT` | `3002` | Server port |
| `APP_ENV` | `development` | Environment mode |
| `MAX_RETRIEVE_PER_DAY` | `3` | Rate limit for retrieves |
| `MAX_STORE_PER_MINUTE` | `10` | Rate limit for stores |
| `AUDIT_LOG_RETENTION_DAYS` | `365` | Audit log retention |
---
## Database Management
### Prisma CLI Commands
```bash
# Generate Prisma client after schema changes
npx prisma generate
# Create a new migration
npx prisma migrate dev --name migration_name
# Apply migrations in production
npx prisma migrate deploy
# Reset database (development only)
npx prisma migrate reset
# Open Prisma Studio
npx prisma studio
# Push schema without migrations (development)
npx prisma db push
```
### Schema Changes Workflow
1. Modify `prisma/schema.prisma`
2. Create migration: `npx prisma migrate dev --name descriptive_name`
3. Test locally
4. Commit migration files
5. Apply in staging/production: `npx prisma migrate deploy`
---
## Debugging
### VSCode Launch Configuration
Add to `.vscode/launch.json`:
```json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Nest.js",
"runtimeArgs": [
"-r",
"ts-node/register",
"-r",
"tsconfig-paths/register"
],
"args": ["${workspaceFolder}/src/main.ts"],
"envFile": "${workspaceFolder}/.env"
},
{
"type": "node",
"request": "launch",
"name": "Debug Jest Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "--config", "jest.config.js"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
```
### Debug Logging
Enable verbose logging:
```typescript
// In main.ts or app.module.ts
app.useLogger(['log', 'error', 'warn', 'debug', 'verbose']);
```
---
## Code Style
### TypeScript Configuration
The project uses strict TypeScript settings:
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
```
### Naming Conventions
| Type | Convention | Example |
|------|------------|---------|
| Files | kebab-case | `backup-share.entity.ts` |
| Classes | PascalCase | `BackupShareEntity` |
| Interfaces | PascalCase with I prefix | `IBackupShareRepository` |
| Functions | camelCase | `storeBackupShare()` |
| Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
| Environment | UPPER_SNAKE_CASE | `DATABASE_URL` |
### File Organization
```
feature/
├── feature.command.ts # Command object
├── feature.handler.ts # Command handler
├── feature.spec.ts # Unit tests
└── index.ts # Barrel export
```
---
## Common Development Tasks
### Adding a New Endpoint
1. Create DTO in `src/api/dto/request/`
2. Add validation decorators
3. Create handler in `src/application/commands/` or `queries/`
4. Add method to controller
5. Write unit tests
6. Write E2E tests
### Adding a New Environment Variable
1. Add to `.env.example` with description
2. Add to `src/config/index.ts`
3. Update this documentation
4. Update deployment configs
### Updating Database Schema
1. Modify `prisma/schema.prisma`
2. Run `npx prisma migrate dev --name description`
3. Update domain entities if needed
4. Update repository mappings
5. Write migration tests
---
## Troubleshooting
### Common Issues
#### Prisma Client Not Generated
```bash
Error: @prisma/client did not initialize yet
```
**Solution:**
```bash
npm run prisma:generate
```
#### Database Connection Failed
```bash
Error: Can't reach database server
```
**Solution:**
1. Check if Docker is running: `docker ps`
2. Check DATABASE_URL in `.env`
3. Restart database: `docker-compose restart backup-db`
#### Port Already in Use
```bash
Error: listen EADDRINUSE: address already in use :::3002
```
**Solution:**
```bash
# Find and kill the process
netstat -ano | findstr :3002
taskkill /PID <PID> /F
```
#### TypeScript Compilation Errors
```bash
# Clear build cache
rm -rf dist
npm run build
```
### Getting Help
- Check existing issues on GitHub
- Review NestJS documentation
- Review Prisma documentation
- Ask in team Slack channel
---
## Best Practices
### Security
1. Never commit secrets to git
2. Use environment variables for all configs
3. Validate all inputs with class-validator
4. Sanitize logs (no sensitive data)
5. Use parameterized queries (Prisma handles this)
### Performance
1. Use database indexes for frequently queried fields
2. Implement pagination for list endpoints
3. Use connection pooling (Prisma default)
4. Cache frequently accessed data if needed
### Code Quality
1. Write unit tests for all handlers
2. Write E2E tests for all endpoints
3. Use TypeScript strict mode
4. Follow DDD principles
5. Keep controllers thin, logic in handlers