Что входит в одну транзакцию в REST API?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Транзакции в REST API: Что это значит и как это работает
Это отличный вопрос, потому что часто разработчики путают REST API запросы с БД транзакциями. Они не одно и то же!
Что такое транзакция в БД
Транзакция (в контексте базы данных) — это группа SQL операций, которые либо все выполняются, либо все откатываются.
# Пример: перевод денег со счёта А на счёт B
from contextlib import contextmanager
@contextmanager
def database_transaction(connection):
try:
yield connection
connection.commit() # если всё OK → коммитим
except Exception as e:
connection.rollback() # если ошибка → откатываем
raise e
# Использование:
with database_transaction(db_connection) as conn:
# Операция 1: снять деньги со счёта А
conn.execute("""
UPDATE accounts SET balance = balance - 100
WHERE account_id = 'A'
""")
# Операция 2: положить деньги на счёт B
conn.execute("""
UPDATE accounts SET balance = balance + 100
WHERE account_id = 'B'
""")
# Обе операции выполнятся вместе или не выполнятся вообще
# Невозможна ситуация, где деньги снялись, но не положились
ACID принципы
Транзакция должна удовлетворять ACID:
| Принцип | Значение | Пример |
|---|---|---|
| Atomicity | Все или ничего | Деньги либо переведены, либо нет |
| Consistency | Данные остаются валидными | После транзакции баланс корректен |
| Isolation | Транзакции не влияют друг на друга | Другой запрос не видит промежуточное состояние |
| Durability | После коммита данные сохранены | Даже если упадёт сервер, данные не потеряются |
Одна транзакция = одна БД операция
Важный момент: одна транзакция в БД — это обычно одна операция (INSERT, UPDATE, DELETE или их группа).
В REST API один запрос обычно = одна БД транзакция:
# REST API endpoint
from fastapi import FastAPI
from sqlalchemy.orm import Session
app = FastAPI()
@app.post("/api/v1/transfers")
async def create_transfer(
from_account: str,
to_account: str,
amount: float,
db: Session
):
# ЭТО ОДНА ТРАНЗАКЦИЯ (одна DB round-trip)
try:
# Операция 1
db.execute(
"UPDATE accounts SET balance = balance - :amount WHERE account_id = :account",
{"amount": amount, "account": from_account}
)
# Операция 2
db.execute(
"UPDATE accounts SET balance = balance + :amount WHERE account_id = :account",
{"amount": amount, "account": to_account}
)
# Коммитим обе операции вместе
db.commit()
return {"status": "success", "transfer_id": "123"}
except Exception as e:
db.rollback() # откатываем обе операции
return {"status": "error", "message": str(e)}
Как это работает в практике
Сценарий: создание заказа с товарами
# Это ОДНА транзакция:
class OrderService:
def create_order(self, user_id: str, items: List[Item], db: Session) -> Order:
try:
# Операция 1: создаём заказ
order = Order(user_id=user_id, total=0)
db.add(order)
db.flush() # получаем order.id без коммита
total = 0
# Операция 2: добавляем товары в заказ
for item in items:
order_item = OrderItem(
order_id=order.id,
product_id=item.product_id,
quantity=item.quantity,
price=item.price
)
db.add(order_item)
total += item.price * item.quantity
# Операция 3: обновляем total
order.total = total
db.commit() # Все 3 операции выполняются вместе
return order
except Exception as e:
db.rollback() # откатываем ВСЕ операции
raise OrderCreationError(str(e))
Важное уточнение: REST API != одна БД операция
Один REST запрос может содержать НЕСКОЛЬКО БД операций в одной транзакции:
# Пример: один POST запрос, несколько SQL операций
@app.post("/api/v1/orders")
async def create_order_with_payment(
user_id: str,
items: List[Item],
payment_info: PaymentInfo,
db: Session
):
# Это ОДНА транзакция, но несколько операций:
try:
# 1. Создаём заказ
order = Order(user_id=user_id)
db.add(order)
db.flush()
# 2. Добавляем товары
for item in items:
db.add(OrderItem(order_id=order.id, product_id=item.id))
# 3. Создаём платёж
payment = Payment(order_id=order.id, amount=total)
db.add(payment)
# 4. Обновляем инвентарь
for item in items:
db.execute(
"UPDATE inventory SET stock = stock - :qty WHERE product_id = :pid",
{"qty": item.quantity, "pid": item.id}
)
# Все 4 операции выполняются вместе
db.commit()
return {"order_id": order.id, "status": "success"}
except Exception:
db.rollback() # откатываем ВСЕ
raise
Несколько REST запросов = несколько транзакций
# Если REST API требует НЕСКОЛЬКО запросов:
# Запрос 1: создание заказа
POST /api/v1/orders
# ↓ Транзакция 1 в БД
# ✅ success
# Запрос 2: добавление товаров в заказ
POST /api/v1/orders/123/items
# ↓ Транзакция 2 в БД
# ❌ ОШИБКА!
# Товары не добавлены, но заказ уже создан
# INCONSISTENT STATE!
# Это проблема распределённых транзакций
Распределённые транзакции (сложный кейс)
Если нужна транзакция через несколько сервисов:
# Сценарий: платёж в одном сервисе, заказ в другом
# ❌ Простой подход (опасно):
def create_order_with_payment(order_data, payment_data):
# Сервис 1: платёж
payment_response = requests.post(
"http://payment-service/api/v1/payments",
json=payment_data
) # ✅ Платёж прошёл
# Сервис 2: заказ
order_response = requests.post(
"http://order-service/api/v1/orders",
json=order_data
) # ❌ ОШИБКА! Платёж уже снят, но заказ не создан
# Деньги потеряны!
# ✅ Правильный подход (Saga pattern):
class OrderSaga:
async def create_order_with_payment(self, data):
payment_id = None
order_id = None
try:
# Шаг 1: создаём платёж
payment = await self.payment_service.create_payment(data.payment)
payment_id = payment.id
# Шаг 2: создаём заказ
order = await self.order_service.create_order(data.order, payment_id)
order_id = order.id
return {"order_id": order_id, "payment_id": payment_id}
except Exception as e:
# Откатываем в обратном порядке
if order_id:
await self.order_service.cancel_order(order_id)
if payment_id:
await self.payment_service.refund_payment(payment_id)
raise
Уровни изоляции транзакций
# SQLAlchemy позволяет выбирать уровень изоляции:
from sqlalchemy.pool import Pool
from sqlalchemy.dialects.postgresql import (
ISOLATION_LEVEL_AUTOCOMMIT,
ISOLATION_LEVEL_READ_COMMITTED,
ISOLATION_LEVEL_REPEATABLE_READ,
ISOLATION_LEVEL_SERIALIZABLE
)
# Пример: строгая изоляция
with db.begin(isolation_level="SERIALIZABLE"):
# Эта транзакция не видит изменения других транзакций
# Самое безопасное, но медленнее
pass
Реальный пример: как я пишу это на production
from sqlalchemy.orm import Session
from typing import TypeVar, Callable
T = TypeVar('T')
class TransactionManager:
def __init__(self, db: Session):
self.db = db
def execute_in_transaction(
self,
operation: Callable[..., T],
*args,
**kwargs
) -> T:
"""Выполнить операцию в транзакции"""
try:
result = operation(self.db, *args, **kwargs)
self.db.commit()
return result
except Exception as e:
self.db.rollback()
# Логируем и пробросываем ошибку
logger.error(f"Transaction failed: {e}")
raise
# Использование:
manager = TransactionManager(db)
def create_order(db: Session, user_id: str, items: List):
order = Order(user_id=user_id)
db.add(order)
db.flush()
for item in items:
db.add(OrderItem(order_id=order.id, product_id=item.id))
return order
order = manager.execute_in_transaction(create_order, user_id="123", items=[...])
Итог
Что входит в одну транзакцию в REST API:
- Одна DB транзакция = все SQL операции одного REST запроса
- Либо все выполняются, либо все откатываются (ACID)
- Несколько REST запросов = несколько транзакций (риск inconsistency)
- Распределённые системы требуют Saga pattern или 2-Phase commit
Для интервью: я понимаю разницу между REST запросом и БД транзакцией, знаю ACID принципы и могу объяснить, почему это важно для data consistency.