← Назад к вопросам

Можно ли к миграции привязать Python код?

1.7 Middle🔥 151 комментариев
#DevOps и инфраструктура#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

GET запросы и создание объектов: теория и практика

Это классический вопрос при разработке REST API. Ответ категоричен: НЕ НУЖНО создавать объекты через GET запросы. Вот почему и какие есть исключения.

Почему GET НЕ подходит для создания объектов

1. Семантика HTTP методов

GET — это метод для получения данных, он идемпотентен (безопасен при повторении). Создание объекта — это изменение состояния сервера, что требует POST, PUT или PATCH:

GET    — получить ресурс (безопасно повторять)
POST   — создать новый ресурс
PUT    — полностью заменить ресурс
PATCH  — частично обновить ресурс
DELETE — удалить ресурс

2. Браузеры кешируют GET запросы

Если вы используете GET для создания объекта, браузер может случайно закешировать результат. При повторном заходе на страницу браузер отправит cachified запрос вместо нового запроса:

# ❌ ПЛОХО: GET для создания
@app.get("/api/users")
def create_user(name: str, email: str):
    user = User.objects.create(name=name, email=email)
    return {"id": user.id, "name": user.name}

# Если клиент случайно откешировал этот запрос, он получит старые данные

3. Прокси и кеширующие сервисы

Internet proxy'ы, CDN, load balancer'ы — все они кешируют GET запросы. Если вы используете GET для создания, множество копий объектов могут быть созданы из-за кеширования на разных уровнях.

4. Безопасность

GET параметры видны в:

  • History браузера
  • Логах сервера
  • Proxy логах
  • Referer header'ах

Лучше не передавать sensitive данные (пароли, API keys) через query параметры. Тело POST запроса не видно в браузер history.

Правильный подход: используй POST

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class UserCreate(BaseModel):
    name: str
    email: str

# ✅ ХОРОШО: POST для создания
@app.post("/api/users")
def create_user(user: UserCreate):
    db_user = db.users.create(name=user.name, email=user.email)
    return {
        "id": db_user.id,
        "name": db_user.name,
        "email": db_user.email
    }

# ✅ GET используем только для получения
@app.get("/api/users/{user_id}")
def get_user(user_id: int):
    return db.users.get(user_id)

Правила REST API

# Создание нового ресурса
POST /api/users
{
  "name": "John",
  "email": "john@example.com"
}
# Response: 201 Created
# Location: /api/users/123

# Получение ресурса
GET /api/users/123
# Response: 200 OK
{
  "id": 123,
  "name": "John",
  "email": "john@example.com"
}

# Обновление ресурса
PUT /api/users/123
{
  "name": "John Doe",
  "email": "johndoe@example.com"
}
# Response: 200 OK

# Удаление ресурса
DELETE /api/users/123
# Response: 204 No Content

Исключения и edge cases

1. Простые триггеры через GET (если действительно безопасно)

Если операция действительно идемпотентна и не создаёт новый ресурс, GET может быть уместен:

# Примеры, где GET уместен:
@app.get("/api/regenerate-cache")
def regenerate_cache():
    # Регенерирует кеш (безопасно повторять)
    cache.clear()
    cache.populate()
    return {"status": "ok"}

@app.get("/api/send-reminder/{user_id}")
def send_reminder(user_id: int):
    # Отправляет напоминание (может повторяться, идемпотентно)
    send_email(user_id, "Не забудьте!")
    return {"status": "sent"}

НО даже в этих случаях лучше использовать POST, чтобы явно показать, что это действие, а не получение данных.

2. GraphQL — другой подход

В GraphQL нет этой проблемы, потому что все запросы идут POST, а различие между query (получение) и mutation (изменение) явное в теле запроса:

# GraphQL mutation для создания
mutation {
  createUser(name: "John", email: "john@example.com") {
    id
    name
    email
  }
}

Практический пример неправильной реализации

# ❌ ПЛОХО: используем GET для создания
@app.get("/api/users")
def create_user(name: str, email: str):
    user = User.objects.create(name=name, email=email)
    return {"id": user.id}

# Проблемы:
# 1. Браузер может закешировать результат
# 2. Proxy'ы будут кешировать на разных уровнях
# 3. Referer header может содержать email
# 4. При повторении запроса может возникнуть ошибка дубликата

Правильная реализация

from fastapi import FastAPI, status
from typing import Optional

app = FastAPI()

class UserCreate(BaseModel):
    name: str
    email: str

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@app.post(
    "/api/users",
    status_code=status.HTTP_201_CREATED,
    response_model=UserResponse
)
async def create_user(user: UserCreate):
    # Проверяем, что пользователь не существует
    existing = await db.users.get_by_email(user.email)
    if existing:
        raise HTTPException(
            status_code=400,
            detail="Email already registered"
        )
    
    # Создаём пользователя
    new_user = await db.users.create(
        name=user.name,
        email=user.email
    )
    
    return new_user

@app.get("/api/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    user = await db.users.get(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

Итоговое правило

GET = получение данных (безопасно, идемпотентно) POST = создание нового ресурса (изменяет состояние)

Это основной принцип REST архитектуры, и его нужно соблюдать для совместимости с браузерами, proxy'ами, кешами и другим инфраструктурным ПО. Нарушение этого принципа приводит к багам, сложностям в отладке и проблемам с масштабированием.