Files
pyisu/arch-node-recommendations.md
2026-03-13 14:39:43 +08:00

655 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Рекомендации по миграции бэкенда на Node.js (Express + TypeORM)
## Обзор текущей архитектуры
### Текущий стек технологий
- **Backend**: FastAPI 0.104.1 (Python 3.11)
- **База данных**: PostgreSQL 15 + SQLAlchemy 2.0 + Alembic
- **Аутентификация**: JWT (access/refresh токены) + bcrypt
- **Frontend**: Vanilla JavaScript SPA с динамической загрузкой модулей
- **Инфраструктура**: Docker Compose, Nginx, Alpine Linux
- **Документация API**: Автоматическая через FastAPI (Swagger/ReDoc)
### Структура бэкенда
```
backend/
├── app/
│ ├── __init__.py
│ ├── main.py # Точка входа, настройка FastAPI
│ ├── auth.py # JWT аутентификация, хеширование паролей
│ ├── database.py # Подключение к БД, сессии SQLAlchemy
│ ├── models.py # SQLAlchemy ORM модели (4 сущности)
│ ├── schemas.py # Pydantic схемы валидации (11 классов)
│ ├── crud.py # CRUD операции (135 строк бизнес-логики)
│ ├── middleware/
│ │ └── cors.py # CORS настройки
│ └── routes/
│ ├── auth.py # Аутентификация (регистрация, вход, выход, me)
│ ├── courses.py # Курсы (список, детали)
│ ├── favorites.py # Избранные курсы (получение, добавление, удаление)
│ ├── progress.py # Прогресс обучения (CRUD)
│ └── stats.py # Статистика системы
├── alembic/ # Миграции базы данных
├── init.sql # Инициализация БД с тестовыми данными
├── requirements.txt # Python зависимости (13 пакетов)
└── Dockerfile # Docker образ Python 3.11
```
### Ключевые сущности данных
1. **User** (`users`): пользователи системы (email, хеш пароля, активность)
2. **Course** (`courses`): обучающие курсы (название, описание, длительность, уровень)
3. **FavoriteCourse** (`favorite_courses`): связь многие-ко-многим пользователей и курсов (избранное)
4. **Progress** (`progress`): прогресс обучения по курсам (пройдено уроков, всего уроков)
### API Endpoints
| Метод | Путь | Описание | Аутентификация |
|-------|------|----------|----------------|
| POST | `/api/auth/register` | Регистрация пользователя | Нет |
| POST | `/api/auth/login` | Вход, получение JWT токенов | Нет |
| POST | `/api/auth/logout` | Выход (символический) | Да |
| GET | `/api/auth/me` | Информация о текущем пользователе | Да |
| GET | `/api/courses` | Список курсов с фильтрацией по уровню | Нет |
| GET | `/api/courses/{id}` | Детали курса | Нет |
| GET | `/api/favorites` | Избранные курсы пользователя | Да |
| POST | `/api/favorites` | Добавить курс в избранное | Да |
| DELETE | `/api/favorites/{id}` | Удалить курс из избранного | Да |
| GET | `/api/progress` | Прогресс по всем курсам пользователя | Да |
| GET | `/api/progress/{id}` | Прогресс по конкретному курсу | Да |
| POST | `/api/progress/{id}` | Обновить прогресс по курсу | Да |
| DELETE | `/api/progress/{id}` | Удалить прогресс по курсу | Да |
| GET | `/api/stats` | Статистика системы (пользователи, курсы) | Нет |
### Аутентификация и авторизация
- **JWT токены**: access token (30 мин) + refresh token (7 дней)
- **Хранение токенов**: фронтенд сохраняет access token в localStorage
- **Передача токенов**: заголовок `Authorization: Bearer <token>`
- **Хеширование паролей**: bcrypt через библиотеку passlib
- **Защита эндпоинтов**: зависимость `get_current_user` в FastAPI
### Инфраструктура
- **Docker Compose**: 3 сервиса (postgres, backend, frontend)
- **Сеть**: изолированная сеть `auth_network`
- **База данных**: персистентный volume `postgres_data`
- **Проксирование**: Nginx раздает статику и проксирует API запросы к бэкенду
- **Hot reload**: для разработки через `uvicorn --reload`
## Анализ для миграции на Node.js
### Преимущества текущей архитектуры
1. **Чистая слоистая архитектура**: модели, схемы, CRUD, маршруты
2. **Автоматическая валидация**: Pydantic схемы с детальными правилами
3. **Автоматическая документация**: Swagger UI через FastAPI
4. **Асинхронная обработка**: поддержка async/await в FastAPI
5. **Типизация**: Python type hints + Pydantic
### Сложности миграции
1. **Потеря автоматической документации** (Swagger) - потребуется Swagger UI для Express
2. **Потеря автоматической валидации** - необходимо реализовать валидацию вручную
3. **Различия в ORM**: SQLAlchemy vs TypeORM (разные подходы к отношениям, миграциям)
4. **Асинхронность**: FastAPI использует настоящую асинхронность, Express - callback/promise
5. **Система зависимостей**: FastAPI Dependency Injection vs Express middleware
### Совместимость с фронтендом
- **API интерфейс должен остаться неизменным** для минимизации изменений фронтенда
- **Структура ответов**: все эндпоинты возвращают `SuccessResponse` или массивы
- **Коды ошибок**: HTTP статусы и структура `ErrorResponse` должны сохраниться
- **Аутентификация**: тот же механизм JWT с access token в localStorage
## Рекомендуемый стек Node.js
### Основные технологии
| Компонент | Технология | Обоснование |
|-----------|------------|-------------|
| **Фреймворк** | Express 4.x | Минималистичный, близкий к текущей архитектуре, большое сообщество |
| **ORM** | TypeORM 0.3.x | TypeScript-ориентированный, поддержка PostgreSQL, миграции, ActiveRecord/DataMapper паттерны |
| **Валидация** | class-validator + class-transformer | Аналог Pydantic, декларативная валидация через декораторы |
| **Аутентификация** | jsonwebtoken + bcrypt | Стандартные библиотеки для JWT и хеширования паролей |
| **Конфигурация** | dotenv + config | Управление переменными окружения в разных средах |
| **Документация API** | swagger-jsdoc + swagger-ui-express | Автоматическая генерация Swagger документации |
| **Миграции** | TypeORM migrations | Встроенная система миграций TypeORM |
| **Логирование** | winston + morgan | Структурированное логирование + HTTP логгирование |
| **Тестирование** | Jest + Supertest | Unit и интеграционные тесты |
### Альтернативы
- **NestJS**: более структурированный, но более сложный для миграции
- **Prisma**: современный ORM, но менее зрелый для сложных отношений
- **Fastify**: более производительный, но менее распространенный
## Пошаговый план миграции
### Этап 1: Подготовка и настройка окружения
1. **Создание нового бэкенда на Node.js** в отдельной директории (например, `backend-node`)
2. **Инициализация проекта**: `npm init`, установка зависимостей
3. **Настройка TypeScript**: tsconfig, структура проекта
4. **Конфигурация Docker**: создание Dockerfile для Node.js
5. **Обновление docker-compose.yml**: замена сервиса backend
### Этап 2: Настройка базы данных и TypeORM
1. **Определение сущностей TypeORM**: преобразование SQLAlchemy моделей в TypeORM Entity
2. **Настройка подключения к БД**: использование того же PostgreSQL
3. **Создание миграций**: перенос init.sql в миграции TypeORM
4. **Тестирование подключения**: проверка работы с существующей БД
### Этап 3: Реализация базовой инфраструктуры
1. **Настройка Express**: middleware (CORS, парсинг JSON, логирование)
2. **Реализация структуры ответов**: SuccessResponse, ErrorResponse
3. **Настройка Swagger документации**: описание всех эндпоинтов
4. **Создание системы конфигурации**: переменные окружения
### Этап 4: Реализация аутентификации
1. **Создание JWT сервиса**: генерация, верификация токенов
2. **Реализация хеширования паролей**: bcrypt
3. **Создание middleware аутентификации**: аналог `get_current_user`
4. **Тестирование аутентификации**: регистрация, вход, защита эндпоинтов
### Этап 5: Реализация бизнес-логики
1. **Репозитории/сервисы**: перенос CRUD операций из `crud.py`
2. **Контроллеры**: реализация всех эндпоинтов из routes
3. **Валидация запросов**: class-validator для DTO
4. **Обработка ошибок**: централизованный обработчик ошибок
### Этап 6: Интеграция и тестирование
1. **Поэтапная замена эндпоинтов**: можно запустить оба бэкенда параллельно
2. **Тестирование фронтенда**: проверка всех сценариев
3. **Нагрузочное тестирование**: сравнение производительности
4. **Миграция данных**: перенос существующих данных (если требуется)
### Этап 7: Деплой и мониторинг
1. **Обновление Docker Compose**: замена образа бэкенда
2. **Настройка мониторинга**: логи, метрики, health checks
3. **Резервное копирование**: стратегия бэкапов PostgreSQL
4. **Документация**: обновление ARCHITECTURE.md
## Структура проекта Node.js
```
backend-node/
├── src/
│ ├── index.ts # Точка входа
│ ├── app.ts # Настройка Express
│ ├── config/
│ │ ├── index.ts # Конфигурация приложения
│ │ ├── database.ts # Конфигурация БД
│ │ └── jwt.ts # Конфигурация JWT
│ ├── entities/ # TypeORM сущности
│ │ ├── User.ts
│ │ ├── Course.ts
│ │ ├── FavoriteCourse.ts
│ │ └── Progress.ts
│ ├── migrations/ # Миграции TypeORM
│ │ ├── 0001-initial-schema.ts
│ │ └── 0002-seed-data.ts
│ ├── middlewares/
│ │ ├── auth.middleware.ts # Аутентификация
│ │ ├── error.middleware.ts # Обработка ошибок
│ │ ├── validation.middleware.ts # Валидация DTO
│ │ └── cors.middleware.ts # CORS
│ ├── controllers/ # Контроллеры (маршруты)
│ │ ├── auth.controller.ts
│ │ ├── courses.controller.ts
│ │ ├── favorites.controller.ts
│ │ ├── progress.controller.ts
│ │ └── stats.controller.ts
│ ├── services/ # Бизнес-логика
│ │ ├── auth.service.ts
│ │ ├── courses.service.ts
│ │ ├── favorites.service.ts
│ │ ├── progress.service.ts
│ │ └── stats.service.ts
│ ├── repositories/ # Работа с БД (опционально)
│ │ ├── user.repository.ts
│ │ ├── course.repository.ts
│ │ └── base.repository.ts
│ ├── dtos/ # Data Transfer Objects
│ │ ├── auth.dto.ts
│ │ ├── courses.dto.ts
│ │ ├── favorites.dto.ts
│ │ ├── progress.dto.ts
│ │ └── common.dto.ts
│ ├── utils/
│ │ ├── api-response.ts # SuccessResponse, ErrorResponse
│ │ ├── logger.ts # Winston логгер
│ │ └── helpers.ts # Вспомогательные функции
│ └── types/ # TypeScript типы
│ └── index.ts
├── tests/ # Тесты
│ ├── unit/
│ └── integration/
├── docker/ # Docker конфигурация
│ └── Dockerfile
├── .env.example # Пример переменных окружения
├── package.json
├── tsconfig.json
├── ormconfig.ts # Конфигурация TypeORM
└── README.md
```
## Примеры кода
### Сущность User (TypeORM)
```typescript
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, OneToMany } from 'typeorm';
import { FavoriteCourse } from './FavoriteCourse';
import { Progress } from './Progress';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 255, unique: true })
email: string;
@Column({ type: 'varchar', length: 255 })
hashedPassword: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@Column({ type: 'boolean', default: true })
isActive: boolean;
@OneToMany(() => FavoriteCourse, favorite => favorite.user)
favoriteCourses: FavoriteCourse[];
@OneToMany(() => Progress, progress => progress.user)
progress: Progress[];
}
```
### DTO с валидацией (class-validator)
```typescript
import { IsEmail, IsString, MinLength, MaxLength, Matches } from 'class-validator';
export class UserCreateDto {
@IsEmail()
email: string;
@IsString()
@MinLength(8)
@MaxLength(100)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: 'Password must contain at least one uppercase letter, one lowercase letter and one number'
})
password: string;
}
export class UserLoginDto {
@IsEmail()
email: string;
@IsString()
password: string;
}
```
### Контроллер аутентификации
```typescript
import { Request, Response, NextFunction } from 'express';
import { AuthService } from '../services/auth.service';
import { UserCreateDto, UserLoginDto } from '../dtos/auth.dto';
import { successResponse, errorResponse } from '../utils/api-response';
import { validate } from '../middlewares/validation.middleware';
export class AuthController {
private authService: AuthService;
constructor() {
this.authService = new AuthService();
}
register = [
validate(UserCreateDto),
async (req: Request, res: Response, next: NextFunction) => {
try {
const userData: UserCreateDto = req.body;
await this.authService.register(userData);
return successResponse(res, {
message: 'User registered successfully',
data: { email: userData.email }
});
} catch (error) {
next(error);
}
}
];
login = [
validate(UserLoginDto),
async (req: Request, res: Response, next: NextFunction) => {
try {
const credentials: UserLoginDto = req.body;
const result = await this.authService.login(credentials);
return successResponse(res, {
message: 'Login successful',
data: result
});
} catch (error) {
next(error);
}
}
];
getMe = [
// authMiddleware применяется в маршрутах
async (req: Request, res: Response, next: NextFunction) => {
try {
const user = (req as any).user; // После authMiddleware
return successResponse(res, {
data: {
email: user.email,
id: user.id,
createdAt: user.createdAt,
isActive: user.isActive
}
});
} catch (error) {
next(error);
}
}
];
}
```
### Сервис аутентификации
```typescript
import { Repository } from 'typeorm';
import { AppDataSource } from '../config/database';
import { User } from '../entities/User';
import { UserCreateDto, UserLoginDto } from '../dtos/auth.dto';
import { JwtService } from './jwt.service';
import { PasswordService } from './password.service';
import { BadRequestError, UnauthorizedError } from '../utils/errors';
export class AuthService {
private userRepository: Repository<User>;
private jwtService: JwtService;
private passwordService: PasswordService;
constructor() {
this.userRepository = AppDataSource.getRepository(User);
this.jwtService = new JwtService();
this.passwordService = new PasswordService();
}
async register(userData: UserCreateDto): Promise<void> {
// Проверка существования пользователя
const existingUser = await this.userRepository.findOne({
where: { email: userData.email }
});
if (existingUser) {
throw new BadRequestError('Email already registered');
}
// Хеширование пароля
const hashedPassword = await this.passwordService.hashPassword(userData.password);
// Создание пользователя
const user = this.userRepository.create({
email: userData.email,
hashedPassword
});
await this.userRepository.save(user);
}
async login(credentials: UserLoginDto): Promise<{ accessToken: string; refreshToken: string; user: any }> {
// Поиск пользователя
const user = await this.userRepository.findOne({
where: { email: credentials.email }
});
if (!user) {
throw new UnauthorizedError('Incorrect email or password');
}
// Проверка пароля
const isValidPassword = await this.passwordService.verifyPassword(
credentials.password,
user.hashedPassword
);
if (!isValidPassword) {
throw new UnauthorizedError('Incorrect email or password');
}
// Генерация токенов
const accessToken = this.jwtService.generateAccessToken(user.email);
const refreshToken = this.jwtService.generateRefreshToken(user.email);
return {
accessToken,
refreshToken,
user: {
email: user.email,
id: user.id,
createdAt: user.createdAt
}
};
}
}
```
## Миграция данных
### Стратегии миграции
1. **Постепенная миграция**: запуск Node.js бэкенда параллельно с Python, переключение по эндпоинтам
2. **Полная замена**: остановка Python бэкенда, запуск Node.js с переносом данных
3. **Канареечное развертывание**: направление части трафика на Node.js бэкенд
### Перенос существующих данных
1. **Схема БД остается той же**, только ORM меняется
2. **Данные пользователей**: пароли захешированы bcrypt - совместимы
3. **Курсы и связи**: прямокопирование таблиц
4. **Миграционный скрипт**: можно написать на TypeScript с использованием TypeORM
### Пример миграционного скрипта
```typescript
// migrate-data.ts
import { createConnection } from 'typeorm';
import { User, Course, FavoriteCourse, Progress } from './src/entities';
async function migrate() {
// Подключение к существующей БД
const connection = await createConnection({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'auth_user',
password: 'auth_password',
database: 'auth_learning',
entities: [User, Course, FavoriteCourse, Progress],
synchronize: false // Не создавать таблицы автоматически
});
// Проверка данных
const userCount = await connection.manager.count(User);
console.log(`Users in database: ${userCount}`);
// Данные уже существуют, можно закрыть соединение
await connection.close();
}
```
## Инфраструктура
### Dockerfile для Node.js
```dockerfile
FROM node:18-alpine
WORKDIR /app
# Установка зависимостей
COPY package*.json ./
RUN npm ci --only=production
# Копирование исходного кода
COPY . .
# Сборка TypeScript
RUN npm run build
# Создание непривилегированного пользователя
RUN addgroup -g 1000 -S nodejs && \
adduser -S nodejs -u 1000 -G nodejs
USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]
```
### Обновленный docker-compose.yml
```yaml
version: '3.8'
services:
postgres:
# Остается без изменений
backend:
build: ./backend-node
container_name: auth_learning_backend_node
environment:
NODE_ENV: production
DATABASE_URL: postgresql://${POSTGRES_USER:-auth_user}:${POSTGRES_PASSWORD:-auth_password}@postgres:5432/${POSTGRES_DB:-auth_learning}
JWT_SECRET: ${JWT_SECRET:-your-jwt-secret-change-in-production}
ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-30}
REFRESH_TOKEN_EXPIRE_DAYS: ${REFRESH_TOKEN_EXPIRE_DAYS:-7}
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
networks:
- auth_network
# Для разработки с hot-reload:
# command: npm run dev
frontend:
# Остается без изменений, но нужно обновить nginx.conf для проксирования на порт 3000
networks:
auth_network:
driver: bridge
volumes:
postgres_data:
```
### Обновление nginx.conf
```nginx
location /api/ {
proxy_pass http://backend:3000; # Изменен порт с 8000 на 3000
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
## Тестирование
### Стратегия тестирования
1. **Юнит-тесты**: сервисы, утилиты (Jest)
2. **Интеграционные тесты**: API эндпоинты (Supertest)
3. **E2E тесты**: полный поток с фронтендом (Cypress/Playwright)
4. **Нагрузочное тестирование**: сравнение производительности с FastAPI
### Пример теста API
```typescript
import request from 'supertest';
import { app } from '../src/app';
describe('Auth API', () => {
describe('POST /api/auth/register', () => {
it('should register a new user', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'Test1234'
});
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.email).toBe('test@example.com');
});
});
});
```
## Риски и их минимизация
### Технические риски
| Риск | Вероятность | Влияние | Стратегия минимизации |
|------|-------------|---------|----------------------|
| **Несовместимость API** | Низкая | Высокое | Точное копирование структур ответов, тестирование каждого эндпоинта |
| **Производительность** | Средняя | Среднее | Нагрузочное тестирование, оптимизация запросов к БД |
| **Потеря данных** | Низкая | Критическое | Резервное копирование БД перед миграцией, поэтапное переключение |
| **Ошибки аутентификации** | Средняя | Высокое | Тщательное тестирование JWT, проверка совместимости bcrypt хешей |
### Организационные риски
1. **Время миграции**: оценка 2-4 недели для одного разработчика
2. **Обучение команды**: если команда не знает TypeScript/Node.js
3. **Поддержка двух кодовых баз**: в период параллельной работы
## Преимущества перехода на Node.js
### Технические преимущества
1. **Единый язык**: JavaScript/TypeScript на фронтенде и бэкенде
2. **Большая экосистема**: npm с огромным количеством пакетов
3. **Высокая производительность**: Node.js хорошо справляется с I/O операциями
4. **Меньшее потребление памяти**: по сравнению с Python + FastAPI
### Бизнес-преимущества
1. **Упрощение найма**: больше разработчиков знают JavaScript чем Python
2. **Более быстрое развитие**: возможность переиспользования кода между фронтендом и бэкендом
3. **Лучшая масштабируемость**: Node.js лучше подходит для микросервисной архитектуры
4. **Снижение затрат на инфраструктуру**: более эффективное использование ресурсов
## Рекомендации по внедрению
### Приоритетность задач
1. **Высокий приоритет**:
- Настройка базовой инфраструктуры (Express, TypeORM, Docker)
- Реализация аутентификации (JWT, bcrypt)
- Миграция основных сущности (User, Course)
2. **Средний приоритет**:
- Реализация оставшихся эндпоинтов
- Настройка Swagger документации
- Интеграционное тестирование
3. **Низкий приоритет**:
- Оптимизация производительности
- Расширенные функции (пагинация, сортировка)
- Мониторинг и логирование
### Контрольные точки
1. **Неделя 1**: Базовая инфраструктура, подключение к БД, простые CRUD операции
2. **Неделя 2**: Аутентификация, защита эндпоинтов, основные маршруты
3. **Неделя 3**: Тестирование, интеграция с фронтендом, документация
4. **Неделя 4**: Постепенное переключение, мониторинг, оптимизация
### Критерии успеха
1. **Все существующие API эндпоинты работают** без изменений фронтенда
2. **Производительность не хуже** FastAPI версии
3. **100% покрытие тестами** критической функциональности
4. **Полная документация** API и архитектуры
## Заключение
Миграция бэкенда с FastAPI на Express + TypeORM является технически выполнимой задачей с четкими преимуществами для долгосрочного развития проекта. Рекомендуемый подход - поэтапная миграция с сохранением обратной совместимости API.
Ключевые факторы успеха:
1. **Сохранение API интерфейса** для минимизации изменений фронтенда
2. **Тщательное тестирование** каждого эндпоинта
3. **Поэтапное развертывание** с возможностью отката
4. **Документирование** всех изменений и решений
Предложенный стек (Express + TypeORM) обеспечивает хороший баланс между производительностью, поддерживаемостью и скоростью разработки, делая его подходящим выбором для данного проекта.