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

Что такое идемпотентность?

2.0 Middle🔥 61 комментариев
#API тестирование#Архитектура приложений

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

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

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

Идемпотентность в разработке и тестировании

Идемпотентность — это свойство операции, при котором повторное выполнение даёт тот же результат, что и однократное. Это фундаментальное свойство для надёжных систем и критично в тестировании.

Определение

Операция является идемпотентной, если:

  • f(x) = f(f(x)) = f(f(f(x))) = ...

То есть, применение операции несколько раз даёт тот же результат, что и применение один раз.

Примеры идемпотентных и неидемпотентных операций

В математике

abs(-5) = 5
abs(abs(-5)) = abs(5) = 5  ✓ идемпотентно

x = 5; x = x  ✓ идемпотентно

В HTTP методах (REST API)

GET — идемпотентен (получение данных не меняет состояние):

GET /users/123  → Получи пользователя 123
GET /users/123  → Получи пользователя 123
GET /users/123  → Получи пользователя 123
Результат одинаковый каждый раз ✓

DELETE — идемпотентен (удаление уже удалённого ресурса):

DELETE /users/123  → Удали пользователя 123 (200 OK)
DELETE /users/123  → Уже удалён (404 Not Found или 204)
Результат: ресурса нет — одинаково ✓

POST — НЕ идемпотентен (каждый вызов создаёт новый ресурс):

POST /users (name=John)  → Создано, ID=1
POST /users (name=John)  → Создано, ID=2 ✗ ДРУГОЙ результат!
POST /users (name=John)  → Создано, ID=3 ✗ ДРУГОЙ результат!

PUT — идемпотентен (обновление на одно и то же значение):

PUT /users/123 (name=John, age=30)  → Обновлено
PUT /users/123 (name=John, age=30)  → Обновлено (идентично)
PUT /users/123 (name=John, age=30)  → Обновлено (идентично) ✓

Почему идемпотентность важна в QA-тестировании

1. Безопасность переповтора

Если тест упадёт и его нужно перезапустить, идемпотентные операции не создадут побочных эффектов или дублирования:

def test_delete_user():
    user_id = create_test_user()
    
    # DELETE идемпотентен, можно вызвать несколько раз
    response = requests.delete(f"/users/{user_id}")
    assert response.status_code in [204, 404]
    
    # Если тест упадёт и его перезапустить, второе удаление
    # тоже вернёт успех (404 Not Found)

2. Надёжность при сетевых сбоях

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

# GET безопасно повторить — он не меняет состояние
def get_with_retry(url, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.get(url)  # ✓ Идемпотентен
            if response.status_code == 200:
                return response
        except requests.ConnectionError:
            if attempt == max_retries - 1:
                raise
            time.sleep(1)

# POST нельзя просто повторить — может быть дублирование!
def post_with_retry(url, data, max_retries=3):  # ✗ Опасно!
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=data)
            if response.status_code in [200, 201]:
                return response
        except requests.ConnectionError:
            # Может быть двойное платёж, двойное создание записи!
            pass

3. Дублирование платежей и операций

В микросервисах и платёжных системах это критично:

# Опасно: если клиент повторит POST, платёж будет дважды
POST /payments
{
    "user_id": 123,
    "amount": 100,
    "currency": "USD"
}
# Если сетевой сбой и клиент повторит — два платежа!

Обеспечение идемпотентности в API

Идемпотентный ключ (Idempotency Key)

Сервер должен отслеживать, выполнял ли он уже этот запрос по уникальному ключу:

# Клиент отправляет уникальный ключ
import uuid

headers = {
    "Idempotency-Key": str(uuid.uuid4())
}

response = requests.post(
    "http://api.example.com/payments",
    json={"amount": 100, "user_id": 123},
    headers=headers
)

# Если клиент повторит с тем же ключом, сервер вернёт старый результат

На стороне сервера (FastAPI + Python)

from fastapi import FastAPI, Header
import uuid
from typing import Optional

app = FastAPI()

# Хранилище выполненных операций (в реальности — в БД/Redis)
completed_operations = {}

@app.post("/payments")
def create_payment(
    amount: float,
    user_id: int,
    idempotency_key: str = Header(...)
):
    # Проверяем, выполняли ли уже этот запрос
    if idempotency_key in completed_operations:
        # Вернуть сохранённый результат
        return completed_operations[idempotency_key]
    
    # Выполняем операцию (платёж)
    payment_id = str(uuid.uuid4())
    result = {
        "id": payment_id,
        "amount": amount,
        "user_id": user_id,
        "status": "completed"
    }
    
    # Сохраняем результат для повторов
    completed_operations[idempotency_key] = result
    
    return result

Выводы

Идемпотентность — это критическое свойство для надёжных систем:

  • GET, DELETE, PUT — должны быть идемпотентны по определению
  • POST — обычно не идемпотентен (создаёт новые ресурсы)
  • Тестирование — должно полагаться на идемпотентные операции для надёжности
  • Распределённые системы — нужны идемпотентные ключи при возможных повторах
  • Платёжные системы — абсолютно критично обеспечить идемпотентность
Что такое идемпотентность? | PrepBro