Старт

This commit is contained in:
2026-03-13 14:39:43 +08:00
commit a2cc480644
88 changed files with 18526 additions and 0 deletions

View File

@@ -0,0 +1,654 @@
# Рекомендации по миграции бэкенда на 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) обеспечивает хороший баланс между производительностью, поддерживаемостью и скоростью разработки, делая его подходящим выбором для данного проекта.