Ответ
Идемпотентность — это свойство операции, при котором её многократное выполнение даёт тот же результат, что и однократное выполнение. Это критически важно в распределённых системах, где возможны повторные вызовы из-за сетевых сбоев, таймаутов или ретраев.
Математическое определение:
f(f(x)) = f(x)
| HTTP-методы и идемпотентность: | Метод | Идемпотентен? | Почему |
|---|---|---|---|
| GET | Да | Получение данных не изменяет состояние сервера | |
| PUT | Да | Повторная отправка того же ресурса даёт идентичный результат | |
| DELETE | Да | Повторное удаление уже удалённого ресурса возвращает тот же статус (404 или 200) | |
| POST | Нет | Каждый вызов создаёт новый ресурс | |
| PATCH | Зависит от реализации | Может быть идемпотентным, если обновляет конкретные поля |
Пример идемпотентного API на C#:
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
[HttpPut("{id}")]
public async Task<IActionResult> UpdateOrder(Guid id, UpdateOrderRequest request)
{
// Проверяем, существует ли заказ
var order = await _repository.GetByIdAsync(id);
if (order == null)
return NotFound();
// Идемпотентность через версионирование или проверку состояния
if (order.Version != request.ExpectedVersion)
return Conflict("Order was modified by another request");
// Обновляем заказ
order.Update(request);
order.Version++;
await _repository.UpdateAsync(order);
return Ok(order);
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteOrder(Guid id)
{
// Идемпотентное удаление: если заказа нет, всё равно возвращаем успех
var order = await _repository.GetByIdAsync(id);
if (order == null)
return Ok(); // Или NoContent() - важно, что результат одинаковый
await _repository.DeleteAsync(id);
return NoContent();
}
}
Идемпотентность в бизнес-логике:
public class PaymentService
{
private readonly IPaymentGateway _gateway;
private readonly IPaymentRepository _repository;
public async Task<PaymentResult> ProcessPayment(PaymentRequest request)
{
// 1. Проверяем, не обработан ли уже этот платеж (идемпотентный ключ)
var existingPayment = await _repository.GetByTransactionId(request.IdempotencyKey);
if (existingPayment != null)
return new PaymentResult { Success = true, TransactionId = existingPayment.Id };
// 2. Создаем запись о платеже в статусе "Processing"
var payment = new Payment(request);
await _repository.AddAsync(payment);
try
{
// 3. Выполняем платеж
var gatewayResult = await _gateway.Charge(request.Amount, request.CardToken);
// 4. Обновляем статус платежа
payment.MarkAsCompleted(gatewayResult.TransactionId);
await _repository.UpdateAsync(payment);
return new PaymentResult { Success = true, TransactionId = payment.Id };
}
catch (PaymentException ex)
{
// 5. При ошибке также сохраняем результат
payment.MarkAsFailed(ex.Message);
await _repository.UpdateAsync(payment);
throw; // Или возвращаем Failure
}
}
}
Техники реализации идемпотентности:
-
Idempotency Key (Ключ идемпотентности)
public class IdempotentRequest { [Required] public Guid IdempotencyKey { get; set; } // Клиент генерирует уникальный ключ public string Data { get; set; } }
// Сервер сохраняет ключ и результат на время TTL
2. **Оптимистичная блокировка (Optimistic Concurrency)**
```csharp
UPDATE Orders
SET Status = 'Completed', Version = Version + 1
WHERE Id = @OrderId AND Version = @ExpectedVersion
-
Проверка состояния перед выполнением
if (order.Status == OrderStatus.Completed) return; // Уже выполнено, ничего не делаем -
Компенсирующие транзакции (Saga Pattern) Для сложных операций, которые не могут быть идемпотентными, реализуйте компенсирующие действия.
Важные нюансы:
- Идемпотентность ≠ Безопасность (Safety): GET безопасен (read-only), но PUT идемпотентен, но не безопасен (изменяет состояние).
- Сетевой уровень: TCP обеспечивает доставку, но не идемпотентность на уровне приложения.
- Побочные эффекты: Логирование, метрики, уведомления могут нарушать идемпотентность.
Практическое правило: Все операции, изменяющие состояние в распределённых системах, должны проектироваться как идемпотентные, если это возможно. Для неидемпотентных операций используйте саги с компенсирующими действиями.
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶