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

Что входит в одну транзакцию в REST API?

2.7 Senior🔥 121 комментариев
#DevOps и инфраструктура#Django

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

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

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

Транзакции в 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:

  1. Одна DB транзакция = все SQL операции одного REST запроса
  2. Либо все выполняются, либо все откатываются (ACID)
  3. Несколько REST запросов = несколько транзакций (риск inconsistency)
  4. Распределённые системы требуют Saga pattern или 2-Phase commit

Для интервью: я понимаю разницу между REST запросом и БД транзакцией, знаю ACID принципы и могу объяснить, почему это важно для data consistency.

Что входит в одну транзакцию в REST API? | PrepBro