Іншими словами: перший виклик змінює систему, а всі наступні — залишають її в тому самому стані, що й після першого виклику.
Де це критично:
📍Background Jobs (напр. Sidekiq / ActiveJob): Мережа може відвалитися, джоба перезапуститься. Код має бути готовим до повторного виконання без дублювання транзакцій.
📍REST API: Запити GET, PUT та DELETE за специфікацією HTTP мають бути ідемпотентними (на відміну від POST).
Приклад у Ruby
Розглянемо сервіс активації користувача.
❌ Неідемпотентний підхід
При кожному виклику метод оновлює базу даних та генерує нові фонові задачі на відправку пошти. Це створює зайве навантаження і спамить користувача.
class ActivateUser
def call(user)
user.update!(active: true, activated_at: Time.current)
UserMailer.activation_success(user).deliver_later
end
end
✅ Ідемпотентний підхід (з урахуванням конкурентності)
Ми робимо умовне оновлення на рівні бази даних через update_all. Метод update_all повертає кількість оновлених рядків (1 або 0). Якщо рядок вже був оновлений іншим паралельним потоком, ми просто виходимо, не відправляючи лист повторно.
class ActivateUser
def call(user)
# Оновлюємо лише якщо active ще false (атомарна операція)
updated_rows = User.where(id: user.id, active: false)
.update_all(active: true, activated_at: Time.current)
return if updated_rows.zero?
# Лист відправиться ОДИН раз, навіть при паралельних запитах
UserMailer.activation_success(user).deliver_later
end
end
Важливо для інтерв‘ю та код-рев‘ю: Ідемпотентність гарантує незмінність стану системи, а не повернутого значення (return value). У другому прикладі перший виклик поверне об’єкт листа, а повторний — nil. Це абсолютно нормально і не порушує ідемпотентність.
TikTok | Instagram | Telegram